@peers-app/peers-sdk 0.29.2 → 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.
@@ -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 = [];
@@ -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 {};
@@ -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.zodPeerId.array().optional().describe("The ids of files attached to this message entry"),
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
- private tableBuild;
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) {
@@ -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.zodPeerId.describe("File ID of the package bundle"),
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.zodPeerId.optional().describe("File ID of the routes bundle"),
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.zodPeerId.optional().describe("File ID of the UI bundle"),
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()
@@ -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";
@@ -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);
@@ -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
- /** The package's definition and manifest, validated together. */
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 { manifest, manifestPath, artifactPath, providerChars: providerSource.length };
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
- this.imports.add("zodPeerId");
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
- ? `zodPeerId.refine((value) => ${JSON.stringify(values)}.includes(value))`
374
- : "zodPeerId";
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:
@@ -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;
@@ -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. */
@@ -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 = zod_1.z.string().refine((value) => {
22
- return (0, peer_id_1.isid)(value);
23
- }, {
24
- message: "Invalid peer id, it must be a 25 character alphanumeric string",
25
- });
26
- exports.zodPeerId._def._fieldType = field_type_1.FieldType.id;
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.29.2",
3
+ "version": "0.30.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"