@peers-app/peers-sdk 0.29.1 → 0.30.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/contracts/contract-resolvers.js +1 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/system/files-contract.d.ts +6 -0
- package/dist/contracts/system/files-contract.js +85 -0
- package/dist/contracts/system/files-shared.d.ts +59 -0
- package/dist/contracts/system/files-shared.js +31 -0
- package/dist/contracts/validate.d.ts +1 -0
- package/dist/contracts/validate.js +6 -1
- package/dist/data/files/files.d.ts +55 -0
- package/dist/data/files/files.js +57 -12
- package/dist/data/messages.d.ts +21 -0
- package/dist/data/messages.js +21 -1
- package/dist/data/orm/sql.data-source.d.ts +2 -1
- package/dist/data/orm/sql.data-source.js +1 -0
- package/dist/data/orm/types.js +12 -0
- package/dist/data/package-versions.js +3 -3
- package/dist/define/index.d.ts +1 -1
- package/dist/define/index.js +2 -1
- package/dist/define/system-contracts.d.ts +9 -0
- package/dist/define/system-contracts.js +9 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/package-build/build-package.d.ts +12 -0
- package/dist/package-build/build-package.js +45 -6
- package/dist/package-build/cli.js +8 -0
- package/dist/package-build/contract-source.js +7 -5
- package/dist/rpc-types.d.ts +8 -1
- package/dist/rpc-types.js +8 -4
- package/dist/system-ids.d.ts +2 -0
- package/dist/system-ids.js +3 -1
- package/dist/types/field-type.d.ts +14 -0
- package/dist/types/field-type.js +14 -1
- package/dist/types/zod-types.d.ts +10 -0
- package/dist/types/zod-types.js +25 -7
- package/package.json +1 -1
|
@@ -211,6 +211,7 @@ async function isSelfTrustedContractCaller(userContext, remoteIdentity) {
|
|
|
211
211
|
}
|
|
212
212
|
const permissionDenied = (call, reason) => new Error(`Permission denied: ${call.member} '${call.name}'.${call.operation} on contract ${call.contractId}${reason ? ` (${reason})` : ""}`);
|
|
213
213
|
const localOnlyContractIds = new Set([
|
|
214
|
+
system_ids_1.filesContractId,
|
|
214
215
|
system_ids_1.groupMembersContractId,
|
|
215
216
|
system_ids_1.hostToolsContractId,
|
|
216
217
|
system_ids_1.desktopToolsContractId,
|
|
@@ -19,6 +19,7 @@ export * from "./system/app-updates-contract";
|
|
|
19
19
|
export { CONTRACT_CATALOG_VERSION, contractCatalogProviderPackageId, getContractCatalogDefinition, type IContractCatalogConsumer, type IContractDefinitionWithPolicy, type IContractProviderInfo, type IContractToolPolicy, type IExposedContractInfo, type IExposedToolInfo, type IGetContractDefinitionInput, type IListContractProvidersInput, } from "./system/contract-catalog-contract";
|
|
20
20
|
export * from "./system/desktop-tools-contract";
|
|
21
21
|
export { DEVICE_OPERATIONS_CONTRACT_VERSION, DEVICE_OPERATIONS_MAX_QUERY_LENGTH, DEVICE_OPERATIONS_MAX_RESULT_BYTES, DEVICE_OPERATIONS_MAX_ROWS, DEVICE_OPERATIONS_MAX_TOOLS, type DeviceOperationCapability, deviceOperationsProviderPackageId, deviceOperationsToolIds, getDeviceOperationsContractDefinition, type IDeviceDatabaseQueryInput, type IDeviceDatabaseQueryResult, type IDeviceOperationStatus, type IDeviceToolListInput, type IDeviceToolLookupInput, type IDeviceToolLookupResult, type IDeviceToolRunInput, } from "./system/device-operations-contract";
|
|
22
|
+
export * from "./system/files-contract";
|
|
22
23
|
export * from "./system/group-members-contract";
|
|
23
24
|
export * from "./system/host-shell-contract";
|
|
24
25
|
export * from "./system/host-tools-contract";
|
package/dist/contracts/index.js
CHANGED
|
@@ -105,6 +105,7 @@ Object.defineProperty(exports, "DEVICE_OPERATIONS_MAX_TOOLS", { enumerable: true
|
|
|
105
105
|
Object.defineProperty(exports, "deviceOperationsProviderPackageId", { enumerable: true, get: function () { return device_operations_contract_1.deviceOperationsProviderPackageId; } });
|
|
106
106
|
Object.defineProperty(exports, "deviceOperationsToolIds", { enumerable: true, get: function () { return device_operations_contract_1.deviceOperationsToolIds; } });
|
|
107
107
|
Object.defineProperty(exports, "getDeviceOperationsContractDefinition", { enumerable: true, get: function () { return device_operations_contract_1.getDeviceOperationsContractDefinition; } });
|
|
108
|
+
__exportStar(require("./system/files-contract"), exports);
|
|
108
109
|
__exportStar(require("./system/group-members-contract"), exports);
|
|
109
110
|
__exportStar(require("./system/host-shell-contract"), exports);
|
|
110
111
|
__exportStar(require("./system/host-tools-contract"), exports);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IContractDefinition } from "../types";
|
|
2
|
+
export * from "./files-shared";
|
|
3
|
+
/** Version of the local-only System Files contract. */
|
|
4
|
+
export declare const FILES_CONTRACT_VERSION = 1;
|
|
5
|
+
/** Return the System Files v1 declaration. Tool identities are derived by the catalog. */
|
|
6
|
+
export declare function getFilesContractDefinition(): IContractDefinition;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.FILES_CONTRACT_VERSION = void 0;
|
|
18
|
+
exports.getFilesContractDefinition = getFilesContractDefinition;
|
|
19
|
+
const system_ids_1 = require("../../system-ids");
|
|
20
|
+
const field_type_1 = require("../../types/field-type");
|
|
21
|
+
const system_contracts_1 = require("../system-contracts");
|
|
22
|
+
const files_shared_1 = require("./files-shared");
|
|
23
|
+
__exportStar(require("./files-shared"), exports);
|
|
24
|
+
/** Version of the local-only System Files contract. */
|
|
25
|
+
exports.FILES_CONTRACT_VERSION = 1;
|
|
26
|
+
const field = (name, type, description, opts = {}) => ({ name, type, description, ...opts });
|
|
27
|
+
/** Return the System Files v1 declaration. Tool identities are derived by the catalog. */
|
|
28
|
+
function getFilesContractDefinition() {
|
|
29
|
+
const size = `${files_shared_1.FILES_MAX_CALL_BYTES / (1024 * 1024)} MiB`;
|
|
30
|
+
return {
|
|
31
|
+
contractId: system_ids_1.filesContractId,
|
|
32
|
+
version: exports.FILES_CONTRACT_VERSION,
|
|
33
|
+
devTag: "dev",
|
|
34
|
+
name: "System Files",
|
|
35
|
+
description: "Save and read files in the caller's own data context.",
|
|
36
|
+
tables: [],
|
|
37
|
+
observables: [],
|
|
38
|
+
events: [],
|
|
39
|
+
tools: [
|
|
40
|
+
{
|
|
41
|
+
name: "save",
|
|
42
|
+
usageDescription: `Save a new file of up to ${size} in this context and return its id.`,
|
|
43
|
+
inputFields: [
|
|
44
|
+
field("name", field_type_1.FieldType.string, "File name, such as avatar.jpg."),
|
|
45
|
+
field("mimeType", field_type_1.FieldType.string, "MIME type, such as image/jpeg.", {
|
|
46
|
+
optional: true,
|
|
47
|
+
}),
|
|
48
|
+
field("bytes", field_type_1.FieldType.blob, `The whole file, at most ${size}.`),
|
|
49
|
+
],
|
|
50
|
+
outputFields: [
|
|
51
|
+
field("fileId", field_type_1.FieldType.id, "Id to read the file with."),
|
|
52
|
+
field("name", field_type_1.FieldType.string, "File name."),
|
|
53
|
+
field("fileSize", field_type_1.FieldType.number, "Size in bytes."),
|
|
54
|
+
field("fileHash", field_type_1.FieldType.string, "Hash of the file's chunk hashes."),
|
|
55
|
+
field("mimeType", field_type_1.FieldType.string, "MIME type.", { optional: true }),
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "read",
|
|
60
|
+
usageDescription: `Read up to ${size} of a file in this context, from an offset.`,
|
|
61
|
+
inputFields: [
|
|
62
|
+
field("fileId", field_type_1.FieldType.id, "File to read."),
|
|
63
|
+
field("offset", field_type_1.FieldType.number, "First byte to return. Defaults to 0.", {
|
|
64
|
+
optional: true,
|
|
65
|
+
}),
|
|
66
|
+
field("length", field_type_1.FieldType.number, `Most bytes to return. Defaults to the rest, up to ${size}.`, {
|
|
67
|
+
optional: true,
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
outputFields: [
|
|
71
|
+
field("bytes", field_type_1.FieldType.blob, "The bytes read."),
|
|
72
|
+
field("fileSize", field_type_1.FieldType.number, "Size of the whole file."),
|
|
73
|
+
field("name", field_type_1.FieldType.string, "File name."),
|
|
74
|
+
field("mimeType", field_type_1.FieldType.string, "MIME type.", { optional: true }),
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
(0, system_contracts_1.registerSystemContract)({
|
|
81
|
+
providerPackageId: system_ids_1.peersSystemPackageId,
|
|
82
|
+
contractId: system_ids_1.filesContractId,
|
|
83
|
+
version: exports.FILES_CONTRACT_VERSION,
|
|
84
|
+
getDefinition: getFilesContractDefinition,
|
|
85
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { IContractConsumerBase } from "../contract-proxy";
|
|
2
|
+
/** Largest file `save` accepts, and the most bytes one `read` returns. */
|
|
3
|
+
export declare const FILES_MAX_CALL_BYTES: number;
|
|
4
|
+
/** Input for {@link IFilesConsumer tools.save}. */
|
|
5
|
+
export interface IFilesSaveInput {
|
|
6
|
+
/** File name, such as `avatar.jpg`. Shown to people; not a path. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** MIME type, such as `image/jpeg`, returned by later reads. */
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
/** The whole file, at most {@link FILES_MAX_CALL_BYTES}. */
|
|
11
|
+
bytes: Uint8Array;
|
|
12
|
+
}
|
|
13
|
+
/** A saved file's description. Keep `fileId` to read it again. */
|
|
14
|
+
export interface IFilesSaveResult {
|
|
15
|
+
fileId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
fileSize: number;
|
|
18
|
+
/** Hash of the file's chunk hashes; equal bytes give an equal hash. */
|
|
19
|
+
fileHash: string;
|
|
20
|
+
mimeType?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Input for {@link IFilesConsumer tools.read}. */
|
|
23
|
+
export interface IFilesReadInput {
|
|
24
|
+
fileId: string;
|
|
25
|
+
/** First byte to return. Defaults to 0. */
|
|
26
|
+
offset?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Most bytes to return: the rest of the file, up to {@link FILES_MAX_CALL_BYTES}, when
|
|
29
|
+
* omitted. A larger `length` is refused; read a larger file in ranges, or with
|
|
30
|
+
* {@link readWholeFile}.
|
|
31
|
+
*/
|
|
32
|
+
length?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Bytes read from a file, with its description. */
|
|
35
|
+
export interface IFilesReadResult {
|
|
36
|
+
bytes: Uint8Array;
|
|
37
|
+
/** Size of the whole file, so a caller knows how many ranges to read. */
|
|
38
|
+
fileSize: number;
|
|
39
|
+
name: string;
|
|
40
|
+
mimeType?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Consumer shape for System Files: save and read files in the caller's own data context.
|
|
44
|
+
* Files sync to the context's other devices, as every file does.
|
|
45
|
+
*/
|
|
46
|
+
export interface IFilesConsumer extends IContractConsumerBase {
|
|
47
|
+
tools: {
|
|
48
|
+
save(input: IFilesSaveInput): Promise<IFilesSaveResult>;
|
|
49
|
+
read(input: IFilesReadInput): Promise<IFilesReadResult>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read a whole file through System Files, in ranges of {@link FILES_MAX_CALL_BYTES}.
|
|
54
|
+
*
|
|
55
|
+
* @param read - The consumer's `read` tool, from a package, a screen or the host.
|
|
56
|
+
* @param fileId - File to read.
|
|
57
|
+
* @returns The file's bytes, name and MIME type.
|
|
58
|
+
*/
|
|
59
|
+
export declare function readWholeFile(read: IFilesConsumer["tools"]["read"], fileId: string): Promise<IFilesReadResult>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FILES_MAX_CALL_BYTES = void 0;
|
|
4
|
+
exports.readWholeFile = readWholeFile;
|
|
5
|
+
// System Files' limits, shapes and read helper, without the contract's registration, so package
|
|
6
|
+
// code can import them from `@peers-app/peers-sdk/define`.
|
|
7
|
+
/** Largest file `save` accepts, and the most bytes one `read` returns. */
|
|
8
|
+
exports.FILES_MAX_CALL_BYTES = 8 * 1024 * 1024;
|
|
9
|
+
/**
|
|
10
|
+
* Read a whole file through System Files, in ranges of {@link FILES_MAX_CALL_BYTES}.
|
|
11
|
+
*
|
|
12
|
+
* @param read - The consumer's `read` tool, from a package, a screen or the host.
|
|
13
|
+
* @param fileId - File to read.
|
|
14
|
+
* @returns The file's bytes, name and MIME type.
|
|
15
|
+
*/
|
|
16
|
+
async function readWholeFile(read, fileId) {
|
|
17
|
+
const first = await read({ fileId, length: exports.FILES_MAX_CALL_BYTES });
|
|
18
|
+
if (first.bytes.length >= first.fileSize)
|
|
19
|
+
return first;
|
|
20
|
+
const bytes = new Uint8Array(first.fileSize);
|
|
21
|
+
bytes.set(first.bytes);
|
|
22
|
+
let offset = first.bytes.length;
|
|
23
|
+
while (offset < first.fileSize) {
|
|
24
|
+
const part = await read({ fileId, offset, length: exports.FILES_MAX_CALL_BYTES });
|
|
25
|
+
if (part.bytes.length === 0)
|
|
26
|
+
throw new Error(`File ${fileId} ended early at ${offset} bytes`);
|
|
27
|
+
bytes.set(part.bytes, offset);
|
|
28
|
+
offset += part.bytes.length;
|
|
29
|
+
}
|
|
30
|
+
return { ...first, bytes };
|
|
31
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { IAlsoImplementsDeclaration, IContractDefinition, IValidationResult
|
|
|
3
3
|
* Validate that an implementation contract satisfies a target contract shape.
|
|
4
4
|
* The implementation must be a superset: all contract tables, tools,
|
|
5
5
|
* observables, and events must be present with compatible shapes.
|
|
6
|
+
* A table field that keeps files in the target must keep them in the implementation too.
|
|
6
7
|
*/
|
|
7
8
|
export declare function validateProviderSatisfiesContract(impl: IContractDefinition, contract: IContractDefinition): IValidationResult;
|
|
8
9
|
/**
|
|
@@ -5,6 +5,7 @@ exports.contractShapeChanges = contractShapeChanges;
|
|
|
5
5
|
exports.validateImmutability = validateImmutability;
|
|
6
6
|
exports.validateAlsoImplements = validateAlsoImplements;
|
|
7
7
|
const lodash_1 = require("lodash");
|
|
8
|
+
const field_type_1 = require("../types/field-type");
|
|
8
9
|
const types_1 = require("./types");
|
|
9
10
|
function ok() {
|
|
10
11
|
return { valid: true, errors: [] };
|
|
@@ -56,7 +57,10 @@ function validateTableFields(contractTable, implTable) {
|
|
|
56
57
|
}
|
|
57
58
|
continue;
|
|
58
59
|
}
|
|
59
|
-
const compat = isFieldCompatible(contractField, implField, "both")
|
|
60
|
+
const compat = isFieldCompatible(contractField, implField, "both") ??
|
|
61
|
+
((0, field_type_1.isFileReferenceField)(contractField) && !(0, field_type_1.isFileReferenceField)(implField)
|
|
62
|
+
? "contract requires a file reference, implementation does not declare one"
|
|
63
|
+
: null);
|
|
60
64
|
if (compat) {
|
|
61
65
|
errors.push({
|
|
62
66
|
kind: "field",
|
|
@@ -150,6 +154,7 @@ function validateEventFields(contractEvent, implEvent) {
|
|
|
150
154
|
* Validate that an implementation contract satisfies a target contract shape.
|
|
151
155
|
* The implementation must be a superset: all contract tables, tools,
|
|
152
156
|
* observables, and events must be present with compatible shapes.
|
|
157
|
+
* A table field that keeps files in the target must keep them in the implementation too.
|
|
153
158
|
*/
|
|
154
159
|
function validateProviderSatisfiesContract(impl, contract) {
|
|
155
160
|
const errors = [];
|
|
@@ -3,6 +3,11 @@ import { Table } from "../orm";
|
|
|
3
3
|
import { type IFile, type IFileInput } from "./file.types";
|
|
4
4
|
import { FileReadStream } from "./file-read-stream";
|
|
5
5
|
import { FileWriteStream } from "./file-write-stream";
|
|
6
|
+
/**
|
|
7
|
+
* The chunked, content-hashed file store of one data context. Rows describe files; the bytes
|
|
8
|
+
* live in chunks on disk, shared by hash, and a device fetches a missing chunk from a connected
|
|
9
|
+
* device when it reads. Write through {@link FilesTable.saveFile}, never the table methods.
|
|
10
|
+
*/
|
|
6
11
|
export declare class FilesTable extends Table<IFile> {
|
|
7
12
|
/** @deprecated Direct inserts forbidden; use saveFile() or saveFileRecord() */
|
|
8
13
|
insert(..._args: Parameters<Table<any>["insert"]>): never;
|
|
@@ -12,6 +17,11 @@ export declare class FilesTable extends Table<IFile> {
|
|
|
12
17
|
delete(..._args: Parameters<Table<any>["delete"]>): never;
|
|
13
18
|
/** @deprecated Direct save forbidden; use saveFile() or saveFileRecord() */
|
|
14
19
|
save(..._args: Parameters<Table<any>["save"]>): never;
|
|
20
|
+
/**
|
|
21
|
+
* Start writing a file in chunks; `finalize()` saves its row.
|
|
22
|
+
*
|
|
23
|
+
* @param metadata - The new file's id, name, size and MIME type.
|
|
24
|
+
*/
|
|
15
25
|
createWriteStream(metadata: IFileInput): Promise<FileWriteStream>;
|
|
16
26
|
/**
|
|
17
27
|
* Note: Use `saveFile` to write a file to disk. This method is for managing
|
|
@@ -20,13 +30,58 @@ export declare class FilesTable extends Table<IFile> {
|
|
|
20
30
|
* @returns The inserted file record
|
|
21
31
|
*/
|
|
22
32
|
saveFileRecord(fileRecord: IFile): Promise<IFile>;
|
|
33
|
+
/**
|
|
34
|
+
* Open a file for chunked reads.
|
|
35
|
+
*
|
|
36
|
+
* @param fileId - File to read.
|
|
37
|
+
* @param preloadChunksCount - Chunks to fetch ahead of the reader.
|
|
38
|
+
* @param skipVerification - Skip checking chunk hashes (only for trusted local data).
|
|
39
|
+
* @returns The stream, or null when no such file row exists.
|
|
40
|
+
*/
|
|
23
41
|
openReadStream(fileId: string, preloadChunksCount?: number, skipVerification?: boolean): Promise<FileReadStream | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Save a whole file: chunk it, store the chunks and write its row.
|
|
44
|
+
*
|
|
45
|
+
* @param metaData - The file's id, name and MIME type. `fileSize` is taken from `data`.
|
|
46
|
+
* @param data - The bytes, or a string saved as UTF-8.
|
|
47
|
+
* @returns The saved row.
|
|
48
|
+
*/
|
|
24
49
|
saveFile(metaData: IFileInput, data: Uint8Array | string): Promise<IFile>;
|
|
50
|
+
/**
|
|
51
|
+
* Read a whole file, fetching missing chunks from connected devices.
|
|
52
|
+
*
|
|
53
|
+
* @param fileId - File to read.
|
|
54
|
+
* @param opts.skipVerification - Skip checking chunk hashes (only for trusted local data).
|
|
55
|
+
* @returns The bytes, or null when no such file row exists.
|
|
56
|
+
*/
|
|
25
57
|
getFileContents(fileId: string, opts?: {
|
|
26
58
|
skipVerification?: boolean;
|
|
27
59
|
}): Promise<Uint8Array | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Delete a file's row, and its index file's. Chunks stay on disk: other files may share them.
|
|
62
|
+
*
|
|
63
|
+
* @param fileId - File to delete. A missing file is not an error.
|
|
64
|
+
*/
|
|
28
65
|
deleteFile(fileId: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Save a large file's chunk hashes as an index file, itself chunked when large.
|
|
68
|
+
*
|
|
69
|
+
* @param chunkHashes - The large file's chunk hashes, in order.
|
|
70
|
+
* @returns The index file's id.
|
|
71
|
+
*/
|
|
29
72
|
createIndexFileRecursively(chunkHashes: string[]): Promise<string>;
|
|
73
|
+
/**
|
|
74
|
+
* Read a large file's chunk hashes back from its index file.
|
|
75
|
+
*
|
|
76
|
+
* @param indexFileId - The index file's id.
|
|
77
|
+
* @returns The chunk hashes, in order.
|
|
78
|
+
*/
|
|
30
79
|
loadChunkHashesRecursively(indexFileId: string): Promise<string[]>;
|
|
31
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* The Files table of a data context.
|
|
83
|
+
*
|
|
84
|
+
* @param dataContext - The context; defaults to the user context's default (the active group,
|
|
85
|
+
* or the personal context). Pass it explicitly in host code.
|
|
86
|
+
*/
|
|
32
87
|
export declare function Files(dataContext?: DataContext): FilesTable;
|
package/dist/data/files/files.js
CHANGED
|
@@ -10,6 +10,11 @@ const table_definitions_system_1 = require("../orm/table-definitions.system");
|
|
|
10
10
|
const file_types_1 = require("./file.types");
|
|
11
11
|
const file_read_stream_1 = require("./file-read-stream");
|
|
12
12
|
const file_write_stream_1 = require("./file-write-stream");
|
|
13
|
+
/**
|
|
14
|
+
* The chunked, content-hashed file store of one data context. Rows describe files; the bytes
|
|
15
|
+
* live in chunks on disk, shared by hash, and a device fetches a missing chunk from a connected
|
|
16
|
+
* device when it reads. Write through {@link FilesTable.saveFile}, never the table methods.
|
|
17
|
+
*/
|
|
13
18
|
class FilesTable extends orm_1.Table {
|
|
14
19
|
/** @deprecated Direct inserts forbidden; use saveFile() or saveFileRecord() */
|
|
15
20
|
insert(..._args) {
|
|
@@ -27,6 +32,11 @@ class FilesTable extends orm_1.Table {
|
|
|
27
32
|
save(..._args) {
|
|
28
33
|
throw new Error("Direct saves forbidden; use saveFile() or saveFileRecord()");
|
|
29
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Start writing a file in chunks; `finalize()` saves its row.
|
|
37
|
+
*
|
|
38
|
+
* @param metadata - The new file's id, name, size and MIME type.
|
|
39
|
+
*/
|
|
30
40
|
async createWriteStream(metadata) {
|
|
31
41
|
return new file_write_stream_1.FileWriteStream(metadata, this);
|
|
32
42
|
}
|
|
@@ -39,6 +49,14 @@ class FilesTable extends orm_1.Table {
|
|
|
39
49
|
async saveFileRecord(fileRecord) {
|
|
40
50
|
return await super.save(fileRecord);
|
|
41
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Open a file for chunked reads.
|
|
54
|
+
*
|
|
55
|
+
* @param fileId - File to read.
|
|
56
|
+
* @param preloadChunksCount - Chunks to fetch ahead of the reader.
|
|
57
|
+
* @param skipVerification - Skip checking chunk hashes (only for trusted local data).
|
|
58
|
+
* @returns The stream, or null when no such file row exists.
|
|
59
|
+
*/
|
|
42
60
|
async openReadStream(fileId, preloadChunksCount, skipVerification) {
|
|
43
61
|
const fileRecord = await this.get(fileId);
|
|
44
62
|
if (!fileRecord) {
|
|
@@ -46,6 +64,13 @@ class FilesTable extends orm_1.Table {
|
|
|
46
64
|
}
|
|
47
65
|
return new file_read_stream_1.FileReadStream(fileRecord, this, preloadChunksCount, skipVerification);
|
|
48
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Save a whole file: chunk it, store the chunks and write its row.
|
|
69
|
+
*
|
|
70
|
+
* @param metaData - The file's id, name and MIME type. `fileSize` is taken from `data`.
|
|
71
|
+
* @param data - The bytes, or a string saved as UTF-8.
|
|
72
|
+
* @returns The saved row.
|
|
73
|
+
*/
|
|
49
74
|
async saveFile(metaData, data) {
|
|
50
75
|
// Use FileWriteStream internally to ensure consistent chunking logic
|
|
51
76
|
const writeStream = await this.createWriteStream({
|
|
@@ -60,6 +85,13 @@ class FilesTable extends orm_1.Table {
|
|
|
60
85
|
// Finalize and return the result
|
|
61
86
|
return await writeStream.finalize();
|
|
62
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Read a whole file, fetching missing chunks from connected devices.
|
|
90
|
+
*
|
|
91
|
+
* @param fileId - File to read.
|
|
92
|
+
* @param opts.skipVerification - Skip checking chunk hashes (only for trusted local data).
|
|
93
|
+
* @returns The bytes, or null when no such file row exists.
|
|
94
|
+
*/
|
|
63
95
|
async getFileContents(fileId, opts) {
|
|
64
96
|
// Use FileReadStream internally to ensure consistent chunk reading logic
|
|
65
97
|
const readStream = await this.openReadStream(fileId, undefined, opts?.skipVerification);
|
|
@@ -69,6 +101,11 @@ class FilesTable extends orm_1.Table {
|
|
|
69
101
|
// Read the entire file using the streaming implementation
|
|
70
102
|
return await readStream.readAll();
|
|
71
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Delete a file's row, and its index file's. Chunks stay on disk: other files may share them.
|
|
106
|
+
*
|
|
107
|
+
* @param fileId - File to delete. A missing file is not an error.
|
|
108
|
+
*/
|
|
72
109
|
async deleteFile(fileId) {
|
|
73
110
|
// Get file metadata from database
|
|
74
111
|
const fileRecord = await super.get(fileId);
|
|
@@ -85,7 +122,12 @@ class FilesTable extends orm_1.Table {
|
|
|
85
122
|
// Delete file record from database
|
|
86
123
|
await super.delete(fileId);
|
|
87
124
|
}
|
|
88
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Save a large file's chunk hashes as an index file, itself chunked when large.
|
|
127
|
+
*
|
|
128
|
+
* @param chunkHashes - The large file's chunk hashes, in order.
|
|
129
|
+
* @returns The index file's id.
|
|
130
|
+
*/
|
|
89
131
|
async createIndexFileRecursively(chunkHashes) {
|
|
90
132
|
// Create JSON content with chunk hashes
|
|
91
133
|
const indexContent = JSON.stringify(chunkHashes);
|
|
@@ -104,7 +146,12 @@ class FilesTable extends orm_1.Table {
|
|
|
104
146
|
await this.saveFile(indexMetadata, indexBuffer);
|
|
105
147
|
return indexFileId;
|
|
106
148
|
}
|
|
107
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Read a large file's chunk hashes back from its index file.
|
|
151
|
+
*
|
|
152
|
+
* @param indexFileId - The index file's id.
|
|
153
|
+
* @returns The chunk hashes, in order.
|
|
154
|
+
*/
|
|
108
155
|
async loadChunkHashesRecursively(indexFileId) {
|
|
109
156
|
// Recursively load the index file
|
|
110
157
|
const indexBuffer = await this.getFileContents(indexFileId);
|
|
@@ -118,18 +165,17 @@ class FilesTable extends orm_1.Table {
|
|
|
118
165
|
}
|
|
119
166
|
exports.FilesTable = FilesTable;
|
|
120
167
|
(0, table_definitions_system_1.registerSystemTableDefinition)(file_types_1.filesMetaData, file_types_1.fileSchema, FilesTable);
|
|
168
|
+
/**
|
|
169
|
+
* The Files table of a data context.
|
|
170
|
+
*
|
|
171
|
+
* @param dataContext - The context; defaults to the user context's default (the active group,
|
|
172
|
+
* or the personal context). Pass it explicitly in host code.
|
|
173
|
+
*/
|
|
121
174
|
function Files(dataContext) {
|
|
122
175
|
return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(file_types_1.filesMetaData, file_types_1.fileSchema);
|
|
123
176
|
}
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
const data = await Files().getFileContents(fileId);
|
|
127
|
-
if (data === null) {
|
|
128
|
-
throw new Error(`File not found: ${fileId}`);
|
|
129
|
-
}
|
|
130
|
-
return Buffer.from(data).toString(encoding);
|
|
131
|
-
};
|
|
132
|
-
// Get file contents as base64 - useful for binary data like images
|
|
177
|
+
// Deprecated RPCs, kept only for Family Hub 0.3.x until its port is Home's version. New code
|
|
178
|
+
// uses System Files, which is bound to the caller's own data context.
|
|
133
179
|
rpc_types_1.rpcServerCalls.getFileContentsBase64 = async (fileId) => {
|
|
134
180
|
const data = await Files().getFileContents(fileId);
|
|
135
181
|
if (data === null) {
|
|
@@ -137,7 +183,6 @@ rpc_types_1.rpcServerCalls.getFileContentsBase64 = async (fileId) => {
|
|
|
137
183
|
}
|
|
138
184
|
return Buffer.from(data).toString("base64");
|
|
139
185
|
};
|
|
140
|
-
// Save a file from the UI - data should be base64 encoded for binary files
|
|
141
186
|
rpc_types_1.rpcServerCalls.saveFile = async (input) => {
|
|
142
187
|
const fileId = input.fileId || (0, utils_1.newid)();
|
|
143
188
|
const encoding = input.encoding || "utf8";
|
package/dist/data/messages.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { DataContext } from "../context/data-context";
|
|
3
|
+
/** A chat entry in a channel or thread, by a user or an assistant acting for them. */
|
|
3
4
|
export declare const messageSchema: z.ZodObject<{
|
|
4
5
|
messageId: z.ZodEffects<z.ZodString, string, string>;
|
|
5
6
|
channelId: z.ZodEffects<z.ZodString, string, string>;
|
|
@@ -34,7 +35,13 @@ export declare const messageSchema: z.ZodObject<{
|
|
|
34
35
|
fileIds?: string[] | undefined;
|
|
35
36
|
vars?: z.objectInputType<{}, z.ZodAny, "strip"> | undefined;
|
|
36
37
|
}>;
|
|
38
|
+
/** One row of the Messages table. */
|
|
37
39
|
export type IMessage = z.infer<typeof messageSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* The Messages table in a data context.
|
|
42
|
+
*
|
|
43
|
+
* @param dataContext - The context to read; defaults to the active one.
|
|
44
|
+
*/
|
|
38
45
|
export declare function Messages(dataContext?: DataContext): import("..").Table<{
|
|
39
46
|
message: string;
|
|
40
47
|
userId: string;
|
|
@@ -52,6 +59,20 @@ interface ISendMessageArgs {
|
|
|
52
59
|
assistantId?: string;
|
|
53
60
|
messageContent: string;
|
|
54
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Post a message as the current user. The target is a message (a reply goes in its thread), a
|
|
64
|
+
* workflow run (a reply to the message that started it), a channel, a group (its default
|
|
65
|
+
* channel), or a user.
|
|
66
|
+
*
|
|
67
|
+
* @param args - The target, the content, and the assistant posting it, if any.
|
|
68
|
+
* @returns The stored message.
|
|
69
|
+
*/
|
|
55
70
|
export declare function sendMessage(args: ISendMessageArgs): Promise<IMessage>;
|
|
71
|
+
/**
|
|
72
|
+
* The variables set on a message and on the message that started its thread, merged in the order
|
|
73
|
+
* they were created: a later message's value replaces an earlier one's.
|
|
74
|
+
*
|
|
75
|
+
* @param message - A message in the thread.
|
|
76
|
+
*/
|
|
56
77
|
export declare function getThreadVars(message: IMessage): Promise<Record<string, any>>;
|
|
57
78
|
export {};
|
package/dist/data/messages.js
CHANGED
|
@@ -14,6 +14,7 @@ const table_definitions_system_1 = require("./orm/table-definitions.system");
|
|
|
14
14
|
const types_1 = require("./orm/types");
|
|
15
15
|
const users_1 = require("./users");
|
|
16
16
|
const workflow_runs_1 = require("./workflow-runs");
|
|
17
|
+
/** A chat entry in a channel or thread, by a user or an assistant acting for them. */
|
|
17
18
|
exports.messageSchema = zod_1.z.object({
|
|
18
19
|
messageId: zod_types_1.zodPeerId,
|
|
19
20
|
channelId: zod_types_1.zodPeerId.describe("The id of the channel this message entry belongs to"),
|
|
@@ -30,7 +31,7 @@ exports.messageSchema = zod_1.z.object({
|
|
|
30
31
|
.date()
|
|
31
32
|
.optional()
|
|
32
33
|
.describe("The time this message entry was last edited, if it was edited"),
|
|
33
|
-
fileIds: zod_types_1.
|
|
34
|
+
fileIds: zod_types_1.zodFileId.array().optional().describe("The ids of files attached to this message entry"),
|
|
34
35
|
vars: zod_types_1.zodAnyObject
|
|
35
36
|
.optional()
|
|
36
37
|
.describe("The variables that are associated with this message entry"),
|
|
@@ -56,9 +57,22 @@ const metaData = {
|
|
|
56
57
|
],
|
|
57
58
|
};
|
|
58
59
|
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.messageSchema);
|
|
60
|
+
/**
|
|
61
|
+
* The Messages table in a data context.
|
|
62
|
+
*
|
|
63
|
+
* @param dataContext - The context to read; defaults to the active one.
|
|
64
|
+
*/
|
|
59
65
|
function Messages(dataContext) {
|
|
60
66
|
return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(metaData, exports.messageSchema);
|
|
61
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Post a message as the current user. The target is a message (a reply goes in its thread), a
|
|
70
|
+
* workflow run (a reply to the message that started it), a channel, a group (its default
|
|
71
|
+
* channel), or a user.
|
|
72
|
+
*
|
|
73
|
+
* @param args - The target, the content, and the assistant posting it, if any.
|
|
74
|
+
* @returns The stored message.
|
|
75
|
+
*/
|
|
62
76
|
async function sendMessage(args) {
|
|
63
77
|
const { channelOrThreadIdOrWorkflowRunId, assistantId, messageContent } = args;
|
|
64
78
|
let channelId = channelOrThreadIdOrWorkflowRunId;
|
|
@@ -98,6 +112,12 @@ async function sendMessage(args) {
|
|
|
98
112
|
});
|
|
99
113
|
return message;
|
|
100
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* The variables set on a message and on the message that started its thread, merged in the order
|
|
117
|
+
* they were created: a later message's value replaces an earlier one's.
|
|
118
|
+
*
|
|
119
|
+
* @param message - A message in the thread.
|
|
120
|
+
*/
|
|
101
121
|
async function getThreadVars(message) {
|
|
102
122
|
const $or = [{ messageId: message.messageId }];
|
|
103
123
|
if (message.messageParentId) {
|
|
@@ -19,7 +19,8 @@ export declare class SQLDataSource<T extends {
|
|
|
19
19
|
private buildTableSql;
|
|
20
20
|
private ensureTableAndIndexesExist;
|
|
21
21
|
private ensureTableIsNewest;
|
|
22
|
-
|
|
22
|
+
/** Shared initialization, reset by a subclass that atomically drops its table and derived data. */
|
|
23
|
+
protected tableBuild: Promise<boolean> | undefined;
|
|
23
24
|
/** Share schema initialization; a failed attempt is evicted so later calls can retry. */
|
|
24
25
|
initTable(force?: boolean): Promise<boolean>;
|
|
25
26
|
get(id: string): Promise<T | undefined>;
|
|
@@ -172,6 +172,7 @@ class SQLDataSource {
|
|
|
172
172
|
await this.ensureTableAndIndexesExist();
|
|
173
173
|
return false;
|
|
174
174
|
}
|
|
175
|
+
/** Shared initialization, reset by a subclass that atomically drops its table and derived data. */
|
|
175
176
|
tableBuild;
|
|
176
177
|
/** Share schema initialization; a failed attempt is evicted so later calls can retry. */
|
|
177
178
|
async initTable(force = false) {
|
package/dist/data/orm/types.js
CHANGED
|
@@ -65,6 +65,10 @@ function fieldValueSchema(field, mode, open) {
|
|
|
65
65
|
case "id": {
|
|
66
66
|
const id = zod_1.z.string().refine((value) => (0, utils_1.isid)(value), { message: "Invalid id" });
|
|
67
67
|
id._def._fieldType = field_type_1.FieldType.id;
|
|
68
|
+
// Keep a file reference a reference when a schema is rebuilt from stored fields.
|
|
69
|
+
if (field.subType) {
|
|
70
|
+
id._def._subType = field.subType;
|
|
71
|
+
}
|
|
68
72
|
return id;
|
|
69
73
|
}
|
|
70
74
|
case "string":
|
|
@@ -261,6 +265,7 @@ function schemaToFields(schema) {
|
|
|
261
265
|
let _def = field._def;
|
|
262
266
|
let isArray;
|
|
263
267
|
let brandedFieldType;
|
|
268
|
+
let brandedSubType;
|
|
264
269
|
while (true) {
|
|
265
270
|
if (_def.typeName === "ZodArray") {
|
|
266
271
|
if (isArray)
|
|
@@ -272,6 +277,9 @@ function schemaToFields(schema) {
|
|
|
272
277
|
if (_def._fieldType) {
|
|
273
278
|
brandedFieldType = _def._fieldType;
|
|
274
279
|
}
|
|
280
|
+
if (_def._subType) {
|
|
281
|
+
brandedSubType = _def._subType;
|
|
282
|
+
}
|
|
275
283
|
if (_def.typeName === "ZodNullable") {
|
|
276
284
|
nullable = true;
|
|
277
285
|
}
|
|
@@ -320,11 +328,15 @@ function schemaToFields(schema) {
|
|
|
320
328
|
// optional/defaulted elements and nested arrays: enumValues describes each immediate element.
|
|
321
329
|
const values = nullable || !enumElements ? undefined : zodEnumValues(valueSchema);
|
|
322
330
|
const enumValues = values?.length ? [...new Set(values)] : undefined;
|
|
331
|
+
// A subtype (`zodFileId`'s "file") describes the branded type, so it goes only where that
|
|
332
|
+
// type was kept.
|
|
333
|
+
const subType = brandedFieldType && type === brandedFieldType ? brandedSubType : undefined;
|
|
323
334
|
const newField = {
|
|
324
335
|
name,
|
|
325
336
|
type,
|
|
326
337
|
isArray,
|
|
327
338
|
optional,
|
|
339
|
+
subType,
|
|
328
340
|
description,
|
|
329
341
|
defaultValue,
|
|
330
342
|
enumValues,
|
|
@@ -58,11 +58,11 @@ const schema = zod_1.z.object({
|
|
|
58
58
|
version: zod_1.z.string().describe('Semantic version string (e.g., "1.0.0")'),
|
|
59
59
|
versionTag: zod_1.z.string().optional().describe('Version tag (e.g., "stable", "beta")'),
|
|
60
60
|
packageVersionHash: zod_1.z.string().describe("Hash of the combined bundle file hashes"),
|
|
61
|
-
packageBundleFileId: zod_types_1.
|
|
61
|
+
packageBundleFileId: zod_types_1.zodFileId.describe("File ID of the package bundle"),
|
|
62
62
|
packageBundleFileHash: zod_1.z.string().describe("Hash of the package bundle file"),
|
|
63
|
-
routesBundleFileId: zod_types_1.
|
|
63
|
+
routesBundleFileId: zod_types_1.zodFileId.optional().describe("File ID of the routes bundle"),
|
|
64
64
|
routesBundleFileHash: zod_1.z.string().optional().describe("Hash of the routes bundle"),
|
|
65
|
-
uiBundleFileId: zod_types_1.
|
|
65
|
+
uiBundleFileId: zod_types_1.zodFileId.optional().describe("File ID of the UI bundle"),
|
|
66
66
|
uiBundleFileHash: zod_1.z.string().optional().describe("Hash of the UI bundle"),
|
|
67
67
|
appNavs: app_nav_1.appNavSchema
|
|
68
68
|
.array()
|
package/dist/define/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { type Observable, observable, type Subscription } from "../observable";
|
|
|
14
14
|
export type { IPackageSaveOptions } from "../package-loader/isolated-runtime";
|
|
15
15
|
export { idFromKey, isid, newid } from "../peer-id";
|
|
16
16
|
export { FieldType, type IField } from "../types/field-type";
|
|
17
|
-
export { zodPeerId } from "../types/zod-types";
|
|
17
|
+
export { zodFileId, zodPeerId } from "../types/zod-types";
|
|
18
18
|
export * from "./context";
|
|
19
19
|
export * from "./contract-ref";
|
|
20
20
|
export * from "./declare-contract";
|
package/dist/define/index.js
CHANGED
|
@@ -14,7 +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
|
-
exports.writePackageVar = exports.userVar = exports.readPackageVar = exports.groupVar = exports.groupUserVar = exports.groupDeviceVar = exports.deviceVar = exports.isImplementedObservable = exports.defineObservable = exports.declareObservable = exports.generatedField = exports.defineEvent = exports.zodPeerId = exports.FieldType = exports.newid = exports.isid = exports.idFromKey = exports.observable = exports.AccessLevel = exports.z = void 0;
|
|
17
|
+
exports.writePackageVar = exports.userVar = exports.readPackageVar = exports.groupVar = exports.groupUserVar = exports.groupDeviceVar = exports.deviceVar = exports.isImplementedObservable = exports.defineObservable = exports.declareObservable = exports.generatedField = exports.defineEvent = exports.zodPeerId = exports.zodFileId = exports.FieldType = exports.newid = exports.isid = exports.idFromKey = exports.observable = exports.AccessLevel = exports.z = void 0;
|
|
18
18
|
/**
|
|
19
19
|
* `@peers-app/peers-sdk/define`: declare an isolated package.
|
|
20
20
|
*
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "newid", { enumerable: true, get: function () { r
|
|
|
36
36
|
var field_type_1 = require("../types/field-type");
|
|
37
37
|
Object.defineProperty(exports, "FieldType", { enumerable: true, get: function () { return field_type_1.FieldType; } });
|
|
38
38
|
var zod_types_1 = require("../types/zod-types");
|
|
39
|
+
Object.defineProperty(exports, "zodFileId", { enumerable: true, get: function () { return zod_types_1.zodFileId; } });
|
|
39
40
|
Object.defineProperty(exports, "zodPeerId", { enumerable: true, get: function () { return zod_types_1.zodPeerId; } });
|
|
40
41
|
__exportStar(require("./context"), exports);
|
|
41
42
|
__exportStar(require("./contract-ref"), exports);
|
|
@@ -138,3 +138,12 @@ export declare const SystemContractCatalog: import("./contract-ref").IContractRe
|
|
|
138
138
|
}>;
|
|
139
139
|
getContractDefinition(input: import("../contracts/system/contract-catalog-contract").IGetContractDefinitionInput): Promise<import("../contracts/system/contract-catalog-contract").IContractDefinitionWithPolicy>;
|
|
140
140
|
}>;
|
|
141
|
+
/**
|
|
142
|
+
* System Files v1: save a file and read it back, in the package's own data context. Files sync
|
|
143
|
+
* to the context's other devices. Declare it with `pkg.consumes(SystemFiles)`.
|
|
144
|
+
*/
|
|
145
|
+
export declare const SystemFiles: import("./contract-ref").IContractRef<{
|
|
146
|
+
save(input: import("../contracts/system/files-shared").IFilesSaveInput): Promise<import("../contracts/system/files-shared").IFilesSaveResult>;
|
|
147
|
+
read(input: import("../contracts/system/files-shared").IFilesReadInput): Promise<import("../contracts/system/files-shared").IFilesReadResult>;
|
|
148
|
+
}>;
|
|
149
|
+
export { FILES_MAX_CALL_BYTES, type IFilesReadInput, type IFilesReadResult, type IFilesSaveInput, type IFilesSaveResult, readWholeFile, } from "../contracts/system/files-shared";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SystemContractCatalog = exports.SystemScheduler = exports.SystemGroupMembers = exports.SystemIdentity = exports.SystemHttp = void 0;
|
|
3
|
+
exports.readWholeFile = exports.FILES_MAX_CALL_BYTES = exports.SystemFiles = exports.SystemContractCatalog = exports.SystemScheduler = exports.SystemGroupMembers = exports.SystemIdentity = exports.SystemHttp = void 0;
|
|
4
4
|
const system_ids_1 = require("../system-ids");
|
|
5
5
|
const contract_ref_1 = require("./contract-ref");
|
|
6
6
|
/**
|
|
@@ -23,3 +23,11 @@ exports.SystemScheduler = (0, contract_ref_1.contractRef)(system_ids_1.scheduler
|
|
|
23
23
|
* `pkg.consumes(SystemContractCatalog)`.
|
|
24
24
|
*/
|
|
25
25
|
exports.SystemContractCatalog = (0, contract_ref_1.contractRef)(system_ids_1.contractCatalogContractId, 1);
|
|
26
|
+
/**
|
|
27
|
+
* System Files v1: save a file and read it back, in the package's own data context. Files sync
|
|
28
|
+
* to the context's other devices. Declare it with `pkg.consumes(SystemFiles)`.
|
|
29
|
+
*/
|
|
30
|
+
exports.SystemFiles = (0, contract_ref_1.contractRef)(system_ids_1.filesContractId, 1);
|
|
31
|
+
var files_shared_1 = require("../contracts/system/files-shared");
|
|
32
|
+
Object.defineProperty(exports, "FILES_MAX_CALL_BYTES", { enumerable: true, get: function () { return files_shared_1.FILES_MAX_CALL_BYTES; } });
|
|
33
|
+
Object.defineProperty(exports, "readWholeFile", { enumerable: true, get: function () { return files_shared_1.readWholeFile; } });
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export { getLogger, type IWorkflowLog, WorkflowLogs, workflowLogSchema, } from "
|
|
|
8
8
|
export { type AnyContractTools, type ContractClientTool, type ContractClientTools, type ContractEventPayloadsOf, type ContractIdOf, type ContractObservableValuesOf, type ContractObservableWritabilityOf, type ContractTableInputsOf, type ContractTableRowsOf, type ContractToolsOf, type ContractVersionOf, contractRef, type IContractClient, type IContractRef, } from "./define/contract-ref";
|
|
9
9
|
export type { IPackageContract, IPackageDefinition, } from "./define/define-package";
|
|
10
10
|
export { buildPackageManifest } from "./define/manifest";
|
|
11
|
+
/** System Files, for package screens as well as providers: `hostContract(SystemFiles)`. */
|
|
12
|
+
export { SystemFiles } from "./define/system-contracts";
|
|
11
13
|
export * from "./device/binary-peer-connection-v2";
|
|
12
14
|
export * from "./device/connection";
|
|
13
15
|
export * from "./device/device";
|
package/dist/index.js
CHANGED
|
@@ -14,7 +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
|
-
exports.buildPackageManifest = exports.contractRef = exports.workflowLogSchema = exports.WorkflowLogs = exports.getLogger = exports.orm = exports.data = void 0;
|
|
17
|
+
exports.SystemFiles = exports.buildPackageManifest = exports.contractRef = exports.workflowLogSchema = exports.WorkflowLogs = exports.getLogger = exports.orm = exports.data = void 0;
|
|
18
18
|
__exportStar(require("./context"), exports);
|
|
19
19
|
__exportStar(require("./contracts"), exports);
|
|
20
20
|
__exportStar(require("./data"), exports);
|
|
@@ -30,6 +30,9 @@ var contract_ref_1 = require("./define/contract-ref");
|
|
|
30
30
|
Object.defineProperty(exports, "contractRef", { enumerable: true, get: function () { return contract_ref_1.contractRef; } });
|
|
31
31
|
var manifest_1 = require("./define/manifest");
|
|
32
32
|
Object.defineProperty(exports, "buildPackageManifest", { enumerable: true, get: function () { return manifest_1.buildPackageManifest; } });
|
|
33
|
+
/** System Files, for package screens as well as providers: `hostContract(SystemFiles)`. */
|
|
34
|
+
var system_contracts_1 = require("./define/system-contracts");
|
|
35
|
+
Object.defineProperty(exports, "SystemFiles", { enumerable: true, get: function () { return system_contracts_1.SystemFiles; } });
|
|
33
36
|
__exportStar(require("./device/binary-peer-connection-v2"), exports);
|
|
34
37
|
__exportStar(require("./device/connection"), exports);
|
|
35
38
|
__exportStar(require("./device/device"), exports);
|
|
@@ -15,6 +15,8 @@ export interface IPackageBuildResult {
|
|
|
15
15
|
artifactPath: string;
|
|
16
16
|
/** Size of the bundled provider source, in characters. */
|
|
17
17
|
providerChars: number;
|
|
18
|
+
/** Things the build accepted but the author likely didn't mean ({@link fileFieldWarnings}). */
|
|
19
|
+
warnings: string[];
|
|
18
20
|
}
|
|
19
21
|
/** A running package watcher. Dispose it to stop watching and release esbuild. */
|
|
20
22
|
export interface IPackageWatcher {
|
|
@@ -28,6 +30,15 @@ export interface IPackageWatcher {
|
|
|
28
30
|
* @param entry - The module's path.
|
|
29
31
|
*/
|
|
30
32
|
export declare function loadPackageModule(root: string, entry: string): Promise<unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Warnings about table fields that look like file references but aren't: a top-level field
|
|
35
|
+
* named like a file id that isn't declared with `zodFileId`, and a `zodFileId` nested in an
|
|
36
|
+
* object field. Neither keeps its files, so Peers may free them. Internal to the build.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
* @param manifest - The package's manifest.
|
|
40
|
+
*/
|
|
41
|
+
export declare function fileFieldWarnings(manifest: IIsolatedPackageManifest): string[];
|
|
31
42
|
/**
|
|
32
43
|
* The generate-manifest step: write `dist/manifest.json` from the package's declarations.
|
|
33
44
|
*
|
|
@@ -36,6 +47,7 @@ export declare function loadPackageModule(root: string, entry: string): Promise<
|
|
|
36
47
|
export declare function generatePackageManifest(buildOptions?: IPackageBuildOptions): Promise<{
|
|
37
48
|
manifest: IIsolatedPackageManifest;
|
|
38
49
|
manifestPath: string;
|
|
50
|
+
warnings: string[];
|
|
39
51
|
}>;
|
|
40
52
|
/**
|
|
41
53
|
* Build the package's manifest from its declarations, validated, without writing it or checking
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.loadPackageModule = loadPackageModule;
|
|
4
|
+
exports.fileFieldWarnings = fileFieldWarnings;
|
|
4
5
|
exports.generatePackageManifest = generatePackageManifest;
|
|
5
6
|
exports.packageManifest = packageManifest;
|
|
6
7
|
exports.buildPackage = buildPackage;
|
|
@@ -18,6 +19,7 @@ const node_module_1 = require("node:module");
|
|
|
18
19
|
const path = require("node:path");
|
|
19
20
|
const manifest_1 = require("../define/manifest");
|
|
20
21
|
const isolated_artifact_1 = require("../package-loader/isolated-artifact");
|
|
22
|
+
const field_type_1 = require("../types/field-type");
|
|
21
23
|
const freeze_contracts_1 = require("./freeze-contracts");
|
|
22
24
|
/** Source text SES rejects before running it, with what to tell the author. */
|
|
23
25
|
const SES_REJECTED_PATTERNS = [
|
|
@@ -129,7 +131,38 @@ async function loadModule(options, entry) {
|
|
|
129
131
|
function validateManifest(manifest, providerSource = "void 0;") {
|
|
130
132
|
(0, isolated_artifact_1.validateIsolatedPackageArtifact)({ manifest: structuredClone(manifest), providerSource }, { strict: true });
|
|
131
133
|
}
|
|
132
|
-
/**
|
|
134
|
+
/** A field name that reads as a file id: `fileId`, `imageFileId`, `fileIds` (not `profileId`). */
|
|
135
|
+
const FILE_ID_NAME = /(?:^file|File)Ids?$/;
|
|
136
|
+
/**
|
|
137
|
+
* Warnings about table fields that look like file references but aren't: a top-level field
|
|
138
|
+
* named like a file id that isn't declared with `zodFileId`, and a `zodFileId` nested in an
|
|
139
|
+
* object field. Neither keeps its files, so Peers may free them. Internal to the build.
|
|
140
|
+
*
|
|
141
|
+
* @internal
|
|
142
|
+
* @param manifest - The package's manifest.
|
|
143
|
+
*/
|
|
144
|
+
function fileFieldWarnings(manifest) {
|
|
145
|
+
const warnings = [];
|
|
146
|
+
const nested = (table, fields, parent) => {
|
|
147
|
+
for (const field of fields ?? []) {
|
|
148
|
+
const path = `${parent}.${field.name}`;
|
|
149
|
+
if ((0, field_type_1.isFileReferenceField)(field)) {
|
|
150
|
+
warnings.push(`Table ${table}: "${path}" is a zodFileId inside an object field, which doesn't keep its files. Only a table's top-level fields do: move it up, or use a table of attachments.`);
|
|
151
|
+
}
|
|
152
|
+
nested(table, field.fields, path);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
for (const table of manifest.tables ?? []) {
|
|
156
|
+
for (const field of table.fields) {
|
|
157
|
+
if (FILE_ID_NAME.test(field.name) && !(0, field_type_1.isFileReferenceField)(field)) {
|
|
158
|
+
warnings.push(`Table ${table.name}: "${field.name}" looks like a file id but isn't declared with zodFileId, so it doesn't keep its files. Declare it with zodFileId and raise the table's versionNumber.`);
|
|
159
|
+
}
|
|
160
|
+
nested(table.name, field.fields, field.name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return warnings;
|
|
164
|
+
}
|
|
165
|
+
/** The package's definition and manifest, validated together, and the build's warnings. */
|
|
133
166
|
async function validPackage(options) {
|
|
134
167
|
// frozen-contracts.json gives the shapes of older versions a contract also implements.
|
|
135
168
|
const definition = await loadDefinition(options);
|
|
@@ -137,7 +170,7 @@ async function validPackage(options) {
|
|
|
137
170
|
frozen: await (0, freeze_contracts_1.readFrozenContracts)(options.root),
|
|
138
171
|
});
|
|
139
172
|
validateManifest(manifest);
|
|
140
|
-
return { definition, manifest };
|
|
173
|
+
return { definition, manifest, warnings: fileFieldWarnings(manifest) };
|
|
141
174
|
}
|
|
142
175
|
/** The package's manifest, validated and checked against the contracts it froze. */
|
|
143
176
|
async function checkedPackage(options) {
|
|
@@ -146,8 +179,8 @@ async function checkedPackage(options) {
|
|
|
146
179
|
return result;
|
|
147
180
|
}
|
|
148
181
|
async function writeManifest(options) {
|
|
149
|
-
const { manifest } = await checkedPackage(options);
|
|
150
|
-
return saveManifest(options, manifest);
|
|
182
|
+
const { manifest, warnings } = await checkedPackage(options);
|
|
183
|
+
return { ...(await saveManifest(options, manifest)), warnings };
|
|
151
184
|
}
|
|
152
185
|
async function saveManifest(options, manifest) {
|
|
153
186
|
const manifestPath = path.join(options.outDir, "manifest.json");
|
|
@@ -241,7 +274,7 @@ async function packageManifest(buildOptions = {}) {
|
|
|
241
274
|
*/
|
|
242
275
|
async function buildPackage(buildOptions = {}) {
|
|
243
276
|
const options = resolveOptions(buildOptions);
|
|
244
|
-
const { definition, manifest } = await checkedPackage(options);
|
|
277
|
+
const { definition, manifest, warnings } = await checkedPackage(options);
|
|
245
278
|
const hasProvider = definition.provides.length > 0 ||
|
|
246
279
|
definition.tables.length > 0 ||
|
|
247
280
|
definition.startHooks.length > 0 ||
|
|
@@ -252,7 +285,13 @@ async function buildPackage(buildOptions = {}) {
|
|
|
252
285
|
const providerSource = output?.outputFiles?.[0]?.text ?? "";
|
|
253
286
|
const artifactPath = await writeArtifact(options, manifest, providerSource);
|
|
254
287
|
const { manifestPath } = await saveManifest(options, manifest);
|
|
255
|
-
return {
|
|
288
|
+
return {
|
|
289
|
+
manifest,
|
|
290
|
+
manifestPath,
|
|
291
|
+
artifactPath,
|
|
292
|
+
providerChars: providerSource.length,
|
|
293
|
+
warnings,
|
|
294
|
+
};
|
|
256
295
|
}
|
|
257
296
|
/**
|
|
258
297
|
* Build once, then rebuild whenever a source file changes. Resolves once watching starts.
|
|
@@ -64,6 +64,11 @@ function summarize(result) {
|
|
|
64
64
|
const tools = manifest.provides.reduce((count, contract) => count + contract.tools.length, 0);
|
|
65
65
|
return `${manifest.provides.length} contract(s), ${tools} tool(s), ${manifest.tables?.length ?? 0} table(s)`;
|
|
66
66
|
}
|
|
67
|
+
/** Print the build's warnings, one per line. */
|
|
68
|
+
function printWarnings(result) {
|
|
69
|
+
for (const warning of result.warnings)
|
|
70
|
+
console.warn(`Warning: ${warning}`);
|
|
71
|
+
}
|
|
67
72
|
function relative(file) {
|
|
68
73
|
return path.relative(process.cwd(), file) || file;
|
|
69
74
|
}
|
|
@@ -88,6 +93,7 @@ async function main(argv) {
|
|
|
88
93
|
throw new Error("--watch applies to build");
|
|
89
94
|
}
|
|
90
95
|
const result = await (0, build_package_1.generatePackageManifest)(options);
|
|
96
|
+
printWarnings(result);
|
|
91
97
|
console.log(`Manifest: ${summarize(result)} -> ${relative(result.manifestPath)}`);
|
|
92
98
|
return 0;
|
|
93
99
|
}
|
|
@@ -97,12 +103,14 @@ async function main(argv) {
|
|
|
97
103
|
console.error(`Build failed: ${result.message}`);
|
|
98
104
|
return;
|
|
99
105
|
}
|
|
106
|
+
printWarnings(result);
|
|
100
107
|
console.log(`Built ${relative(result.artifactPath)} (${summarize(result)}; SDK ${sdk_version_1.SDK_VERSION}; provider ${Math.round(result.providerChars / 1024)} KB)`);
|
|
101
108
|
});
|
|
102
109
|
console.log("Watching for changes");
|
|
103
110
|
return new Promise(() => undefined);
|
|
104
111
|
}
|
|
105
112
|
const result = await (0, build_package_1.buildPackage)(options);
|
|
113
|
+
printWarnings(result);
|
|
106
114
|
console.log(`Built ${relative(result.artifactPath)} (${summarize(result)}; SDK ${sdk_version_1.SDK_VERSION}; provider ${Math.round(result.providerChars / 1024)} KB)`);
|
|
107
115
|
return 0;
|
|
108
116
|
}
|
|
@@ -316,7 +316,7 @@ class Code {
|
|
|
316
316
|
(field.enumValues !== undefined &&
|
|
317
317
|
new Set(field.enumValues).size !== field.enumValues.length) ||
|
|
318
318
|
field.defaultValue !== undefined ||
|
|
319
|
-
field.subType !== undefined ||
|
|
319
|
+
(field.subType !== undefined && !(0, field_type_1.isFileReferenceField)(field)) ||
|
|
320
320
|
field.optional === false ||
|
|
321
321
|
field.isArray === false ||
|
|
322
322
|
field.description === "") {
|
|
@@ -367,11 +367,13 @@ class Code {
|
|
|
367
367
|
return values?.length && new Set(values).size === 1
|
|
368
368
|
? `z.literal(${JSON.stringify(values[0])})`
|
|
369
369
|
: "z.boolean()";
|
|
370
|
-
case field_type_1.FieldType.id:
|
|
371
|
-
|
|
370
|
+
case field_type_1.FieldType.id: {
|
|
371
|
+
const idSchema = (0, field_type_1.isFileReferenceField)(field) ? "zodFileId" : "zodPeerId";
|
|
372
|
+
this.imports.add(idSchema);
|
|
372
373
|
return values?.length
|
|
373
|
-
?
|
|
374
|
-
:
|
|
374
|
+
? `${idSchema}.refine((value) => ${JSON.stringify(values)}.includes(value))`
|
|
375
|
+
: idSchema;
|
|
376
|
+
}
|
|
375
377
|
case field_type_1.FieldType.Date:
|
|
376
378
|
return "z.date()";
|
|
377
379
|
case field_type_1.FieldType.blob:
|
package/dist/rpc-types.d.ts
CHANGED
|
@@ -29,8 +29,15 @@ export interface IRemoteLogsQueryInput {
|
|
|
29
29
|
export declare const rpcServerCalls: {
|
|
30
30
|
encryptData: (value: string, groupId?: string) => Promise<string>;
|
|
31
31
|
tableMethodCall: (dataContextId: string, tableName: string, methodName: string, ...args: any[]) => Promise<any>;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* @deprecated Use System Files (`SystemFiles`, `readWholeFile`). Kept only for Family Hub
|
|
34
|
+
* 0.3.x; deleted once its port is Home's version.
|
|
35
|
+
*/
|
|
33
36
|
getFileContentsBase64: (fileId: string) => Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated Use System Files (`SystemFiles`). Kept only for Family Hub 0.3.x; deleted once
|
|
39
|
+
* its port is Home's version.
|
|
40
|
+
*/
|
|
34
41
|
saveFile: (input: {
|
|
35
42
|
name: string;
|
|
36
43
|
mimeType?: string;
|
package/dist/rpc-types.js
CHANGED
|
@@ -14,11 +14,15 @@ function rpcStub(rpcName) {
|
|
|
14
14
|
exports.rpcServerCalls = {
|
|
15
15
|
encryptData: rpcStub("encryptData"),
|
|
16
16
|
tableMethodCall: rpcStub("tableMethodCall"),
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated Use System Files (`SystemFiles`, `readWholeFile`). Kept only for Family Hub
|
|
19
|
+
* 0.3.x; deleted once its port is Home's version.
|
|
20
|
+
*/
|
|
20
21
|
getFileContentsBase64: rpcStub("getFileContentsBase64"),
|
|
21
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Use System Files (`SystemFiles`). Kept only for Family Hub 0.3.x; deleted once
|
|
24
|
+
* its port is Home's version.
|
|
25
|
+
*/
|
|
22
26
|
saveFile: rpcStub("saveFile"),
|
|
23
27
|
deleteLocalDatabase: rpcStub("deleteLocalDatabase"),
|
|
24
28
|
/** Returns info about the pre-migration data directory, or `null` if already cleaned up. */
|
package/dist/system-ids.d.ts
CHANGED
|
@@ -215,3 +215,5 @@ export declare const packagesContractId = "00mvdhk66tdqrj3hy0bacmj32";
|
|
|
215
215
|
export declare const hostShellContractId = "00mveexhw7ezjfghxomlo011g";
|
|
216
216
|
/** Local-only System Group Members v1; allocated once with newid(). */
|
|
217
217
|
export declare const groupMembersContractId = "00mvzfzkuf4djwjrsxq9cnaxt";
|
|
218
|
+
/** Local-only System Files v1: save and read files in the caller's own context; allocated once with newid(). */
|
|
219
|
+
export declare const filesContractId = "00mw1pvs1eupopu6sttetmki0";
|
package/dist/system-ids.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.groupMembersContractId = exports.hostShellContractId = exports.packagesContractId = exports.desktopToolsContractId = exports.hostToolsContractId = exports.toolCatalogContractId = exports.schedulerToolIds = exports.schedulerContractId = exports.contractCatalogToolIds = exports.contractCatalogContractId = exports.httpToolIds = exports.httpContractId = exports.localFoldersToolIds = exports.localFoldersContractId = exports.identityToolIds = exports.identityContractId = exports.invitesToolIds = exports.invitesContractId = exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.syncCheckContractId = exports.appUpdatesContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCoreRevokedPublishPublicKeys = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.createGroupToolId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
|
|
3
|
+
exports.filesContractId = exports.groupMembersContractId = exports.hostShellContractId = exports.packagesContractId = exports.desktopToolsContractId = exports.hostToolsContractId = exports.toolCatalogContractId = exports.schedulerToolIds = exports.schedulerContractId = exports.contractCatalogToolIds = exports.contractCatalogContractId = exports.httpToolIds = exports.httpContractId = exports.localFoldersToolIds = exports.localFoldersContractId = exports.identityToolIds = exports.identityContractId = exports.invitesToolIds = exports.invitesContractId = exports.toolAccessControlToolIds = exports.toolAccessControlContractId = exports.syncCheckContractId = exports.appUpdatesContractId = exports.networkControlContractId = exports.networkDiagnosticsContractId = exports.deviceOperationsContractId = exports.logsContractId = exports.peersSystemPackageId = exports.peersCoreRevokedPublishPublicKeys = exports.peersCorePublishPublicKey = exports.peersCorePackageRepoUrl = exports.peersCorePackageId = exports.createGroupToolId = exports.queryDatabaseToolId = exports.runWorkflowToolId = exports.defaultSendMessageToolId = exports.defaultAssistantRunnerToolId = exports.adminAssistantId = exports.openCodeAssistantId = exports.defaultAssistantId = exports.peersRootUserId = void 0;
|
|
4
4
|
/** Stable user ID that owns built-in Peers system records. */
|
|
5
5
|
exports.peersRootUserId = "000peers0user000000000001";
|
|
6
6
|
/** Default assistant used when no user-selected assistant is available. */
|
|
@@ -222,3 +222,5 @@ exports.packagesContractId = "00mvdhk66tdqrj3hy0bacmj32";
|
|
|
222
222
|
exports.hostShellContractId = "00mveexhw7ezjfghxomlo011g";
|
|
223
223
|
/** Local-only System Group Members v1; allocated once with newid(). */
|
|
224
224
|
exports.groupMembersContractId = "00mvzfzkuf4djwjrsxq9cnaxt";
|
|
225
|
+
/** Local-only System Files v1: save and read files in the caller's own context; allocated once with newid(). */
|
|
226
|
+
exports.filesContractId = "00mw1pvs1eupopu6sttetmki0";
|
|
@@ -32,6 +32,10 @@ export interface IField {
|
|
|
32
32
|
optional?: boolean;
|
|
33
33
|
/** The value is an array of `type`; `fields`, `enumValues` describe each element. */
|
|
34
34
|
isArray?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* What the value means beyond its type. {@link FILE_SUBTYPE} on an `id` field makes it a file
|
|
37
|
+
* reference ({@link isFileReferenceField}); `"markdown"` on a string asks UIs to render it.
|
|
38
|
+
*/
|
|
35
39
|
subType?: string;
|
|
36
40
|
/** An object's own fields. Absent: any object. Only on `object`; other keys may be present. */
|
|
37
41
|
fields?: IField[];
|
|
@@ -52,3 +56,13 @@ export declare const fieldSchema: z.ZodObject<{
|
|
|
52
56
|
fields: z.ZodOptional<z.ZodLazy<z.ZodArray<z.ZodType<IField>>>>;
|
|
53
57
|
enumValues: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
54
58
|
}, "strip", z.ZodTypeAny, IField, IField>;
|
|
59
|
+
/** The {@link IField.subType} of an `id` field that names a file: what `zodFileId` declares. */
|
|
60
|
+
export declare const FILE_SUBTYPE = "file";
|
|
61
|
+
/**
|
|
62
|
+
* True when a table field references files: an `id` field, or an array of them, with
|
|
63
|
+
* {@link FILE_SUBTYPE}. Only a table's top-level fields count; a file id nested in an object
|
|
64
|
+
* field isn't a reference.
|
|
65
|
+
*
|
|
66
|
+
* @param field - One of a table definition's top-level fields.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isFileReferenceField(field: Pick<IField, "type" | "subType">): boolean;
|
package/dist/types/field-type.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fieldSchema = exports.FieldType = void 0;
|
|
3
|
+
exports.FILE_SUBTYPE = exports.fieldSchema = exports.FieldType = void 0;
|
|
4
|
+
exports.isFileReferenceField = isFileReferenceField;
|
|
4
5
|
const zod_1 = require("zod");
|
|
5
6
|
//export type FieldType = 'id' | 'string' | 'number' | 'boolean' | 'Date' | 'object' | 'blob' | 'any';
|
|
6
7
|
/** The value types a Peers IR field can have. */
|
|
@@ -27,3 +28,15 @@ exports.fieldSchema = zod_1.z.object({
|
|
|
27
28
|
fields: zod_1.z.lazy(() => zod_1.z.array(exports.fieldSchema)).optional(),
|
|
28
29
|
enumValues: zod_1.z.array(zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean()])).optional(),
|
|
29
30
|
});
|
|
31
|
+
/** The {@link IField.subType} of an `id` field that names a file: what `zodFileId` declares. */
|
|
32
|
+
exports.FILE_SUBTYPE = "file";
|
|
33
|
+
/**
|
|
34
|
+
* True when a table field references files: an `id` field, or an array of them, with
|
|
35
|
+
* {@link FILE_SUBTYPE}. Only a table's top-level fields count; a file id nested in an object
|
|
36
|
+
* field isn't a reference.
|
|
37
|
+
*
|
|
38
|
+
* @param field - One of a table definition's top-level fields.
|
|
39
|
+
*/
|
|
40
|
+
function isFileReferenceField(field) {
|
|
41
|
+
return field.type === FieldType.id && field.subType === exports.FILE_SUBTYPE;
|
|
42
|
+
}
|
|
@@ -9,6 +9,16 @@ export declare const zodAnyObjectOrArray: z.ZodUnion<[z.ZodObject<{}, "strip", z
|
|
|
9
9
|
* records the field as an id.
|
|
10
10
|
*/
|
|
11
11
|
export declare const zodPeerId: z.ZodEffects<z.ZodString, string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* A file's id, as `SystemFiles.save` returns it: a {@link zodPeerId} marked as a file reference
|
|
14
|
+
* (`subType: "file"`). A synced table's top-level field declared with it, or with an array of it,
|
|
15
|
+
* keeps the files it names: Peers frees a file's bytes only some time after no such field names
|
|
16
|
+
* it. Local-only tables, package variables, plain string fields, and nested object fields
|
|
17
|
+
* don't declare references.
|
|
18
|
+
*
|
|
19
|
+
* Changing a field to `zodFileId` changes the table's definition, so raise its `versionNumber`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const zodFileId: z.ZodEffects<z.ZodString, string, string>;
|
|
12
22
|
/** Any zod object schema. */
|
|
13
23
|
export type ZodObjectType = z.ZodObject<any>;
|
|
14
24
|
/** Any zod array schema. */
|
package/dist/types/zod-types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.zodPeerId = exports.zodAnyObjectOrArray = exports.zodAnyObject = exports.z = void 0;
|
|
3
|
+
exports.zodFileId = exports.zodPeerId = exports.zodAnyObjectOrArray = exports.zodAnyObject = exports.z = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const peer_id_1 = require("../peer-id");
|
|
6
6
|
const field_type_1 = require("./field-type");
|
|
@@ -14,13 +14,31 @@ Object.defineProperty(exports, "z", { enumerable: true, get: function () { retur
|
|
|
14
14
|
exports.zodAnyObject = zod_1.z.object({}).catchall(zod_1.z.any());
|
|
15
15
|
/** Any plain object or any array. */
|
|
16
16
|
exports.zodAnyObjectOrArray = zod_1.z.union([exports.zodAnyObject, zod_1.z.array(zod_1.z.any())]);
|
|
17
|
+
/** A new peer-id schema, marked as {@link FieldType.id} and, given one, a subtype. */
|
|
18
|
+
function peerIdSchema(subType) {
|
|
19
|
+
const schema = zod_1.z.string().refine((value) => {
|
|
20
|
+
return (0, peer_id_1.isid)(value);
|
|
21
|
+
}, {
|
|
22
|
+
message: "Invalid peer id, it must be a 25 character alphanumeric string",
|
|
23
|
+
});
|
|
24
|
+
schema._def._fieldType = field_type_1.FieldType.id;
|
|
25
|
+
if (subType) {
|
|
26
|
+
schema._def._subType = subType;
|
|
27
|
+
}
|
|
28
|
+
return schema;
|
|
29
|
+
}
|
|
17
30
|
/**
|
|
18
31
|
* A 25-character peer id from `newid()`. Marked as {@link FieldType.id}, so `schemaToFields`
|
|
19
32
|
* records the field as an id.
|
|
20
33
|
*/
|
|
21
|
-
exports.zodPeerId =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
exports.zodPeerId = peerIdSchema();
|
|
35
|
+
/**
|
|
36
|
+
* A file's id, as `SystemFiles.save` returns it: a {@link zodPeerId} marked as a file reference
|
|
37
|
+
* (`subType: "file"`). A synced table's top-level field declared with it, or with an array of it,
|
|
38
|
+
* keeps the files it names: Peers frees a file's bytes only some time after no such field names
|
|
39
|
+
* it. Local-only tables, package variables, plain string fields, and nested object fields
|
|
40
|
+
* don't declare references.
|
|
41
|
+
*
|
|
42
|
+
* Changing a field to `zodFileId` changes the table's definition, so raise its `versionNumber`.
|
|
43
|
+
*/
|
|
44
|
+
exports.zodFileId = peerIdSchema(field_type_1.FILE_SUBTYPE);
|