@pylonsync/sdk 0.21.0 → 0.22.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/index.d.ts +20 -0
- package/package.json +1 -1
- package/src/field-max-length.test.ts +39 -0
- package/src/index.ts +37 -0
package/dist/index.d.ts
CHANGED
|
@@ -74,6 +74,14 @@ export interface FieldDefinition {
|
|
|
74
74
|
* `serverOnly`, which hides a field from ALL client surfaces.
|
|
75
75
|
*/
|
|
76
76
|
syncOmit?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Longest value the field accepts, in characters (Unicode code
|
|
79
|
+
* points). Set with `field.string().max(n)` or
|
|
80
|
+
* `field.richtext().max(n)`. The server rejects a longer value with
|
|
81
|
+
* `FIELD_TOO_LONG` on every write path: the entity API, sync push,
|
|
82
|
+
* `ctx.db` in functions, and collaborative (CRDT) edits.
|
|
83
|
+
*/
|
|
84
|
+
maxLength?: number;
|
|
77
85
|
/**
|
|
78
86
|
* When true, the field is **set on insert but cannot be changed
|
|
79
87
|
* by client updates**. The framework rejects any `PATCH`/`PUT`
|
|
@@ -162,6 +170,15 @@ interface FieldBuilder {
|
|
|
162
170
|
* editor still gets it via `db.get("Variant", id)`.
|
|
163
171
|
*/
|
|
164
172
|
syncOmit(): FieldBuilder;
|
|
173
|
+
/**
|
|
174
|
+
* Cap the value at `n` characters (Unicode code points). String and
|
|
175
|
+
* richtext fields only. See [`FieldDefinition.maxLength`].
|
|
176
|
+
*
|
|
177
|
+
* Example: `bio: field.string().max(280)` stops any client, guests
|
|
178
|
+
* included, from storing a longer bio that every other client would
|
|
179
|
+
* then sync.
|
|
180
|
+
*/
|
|
181
|
+
max(n: number): FieldBuilder;
|
|
165
182
|
/**
|
|
166
183
|
* Mark the field as set-on-insert-only. See [`FieldDefinition.readonly`]
|
|
167
184
|
* for the full semantics.
|
|
@@ -543,6 +560,9 @@ export interface ManifestField {
|
|
|
543
560
|
/** Set when the field is `field.X().syncOmit()` — see
|
|
544
561
|
* [`FieldDefinition.syncOmit`]. Omitted by default. */
|
|
545
562
|
syncOmit?: boolean;
|
|
563
|
+
/** Set by `.max(n)` — see [`FieldDefinition.maxLength`]. Omitted when
|
|
564
|
+
* the field has no limit. */
|
|
565
|
+
maxLength?: number;
|
|
546
566
|
/** Set when the field is `field.X().readonly()` — see
|
|
547
567
|
* [`FieldDefinition.readonly`]. Omitted by default. */
|
|
548
568
|
readonly?: boolean;
|
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `field.string().max(n)` manifest contract:
|
|
3
|
+
* - the limit serializes as `maxLength` (the Rust `ManifestField`
|
|
4
|
+
* reads that key), and only when set
|
|
5
|
+
* - it composes with the other modifiers in any order
|
|
6
|
+
* - it is refused on non-text fields and for non-positive limits
|
|
7
|
+
*/
|
|
8
|
+
import { expect, test } from "bun:test";
|
|
9
|
+
import { entitiesToManifest, field } from "./index";
|
|
10
|
+
|
|
11
|
+
test(".max(n) serializes as maxLength on string and richtext fields", () => {
|
|
12
|
+
const [ent] = entitiesToManifest([
|
|
13
|
+
{
|
|
14
|
+
name: "Post",
|
|
15
|
+
fields: {
|
|
16
|
+
title: field.string().max(120),
|
|
17
|
+
body: field.richtext().optional().max(20_000),
|
|
18
|
+
status: field.enum(["draft", "live"]).max(5),
|
|
19
|
+
slug: field.string(),
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
]);
|
|
23
|
+
const byName = (n: string) => ent.fields.find((f) => f.name === n)!;
|
|
24
|
+
expect(byName("title").maxLength).toBe(120);
|
|
25
|
+
expect(byName("body").maxLength).toBe(20_000);
|
|
26
|
+
expect(byName("body").optional).toBe(true);
|
|
27
|
+
expect(byName("status").maxLength).toBe(5);
|
|
28
|
+
// No limit, no key: unannotated manifests are unchanged.
|
|
29
|
+
expect("maxLength" in byName("slug")).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test(".max(n) is refused on other field types and for bad limits", () => {
|
|
33
|
+
expect(() => (field.int() as { max(n: number): unknown }).max(5)).toThrow(/field.string/);
|
|
34
|
+
expect(() => field.json().max(5)).toThrow(/field.string/);
|
|
35
|
+
expect(() => field.string().max(0)).toThrow(/positive integer/);
|
|
36
|
+
expect(() => field.string().max(1.5)).toThrow(/positive integer/);
|
|
37
|
+
expect(() => field.string().max(-1)).toThrow(/positive integer/);
|
|
38
|
+
expect(() => field.string().max(1)).not.toThrow();
|
|
39
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -101,6 +101,14 @@ export interface FieldDefinition {
|
|
|
101
101
|
* `serverOnly`, which hides a field from ALL client surfaces.
|
|
102
102
|
*/
|
|
103
103
|
syncOmit?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Longest value the field accepts, in characters (Unicode code
|
|
106
|
+
* points). Set with `field.string().max(n)` or
|
|
107
|
+
* `field.richtext().max(n)`. The server rejects a longer value with
|
|
108
|
+
* `FIELD_TOO_LONG` on every write path: the entity API, sync push,
|
|
109
|
+
* `ctx.db` in functions, and collaborative (CRDT) edits.
|
|
110
|
+
*/
|
|
111
|
+
maxLength?: number;
|
|
104
112
|
/**
|
|
105
113
|
* When true, the field is **set on insert but cannot be changed
|
|
106
114
|
* by client updates**. The framework rejects any `PATCH`/`PUT`
|
|
@@ -190,6 +198,15 @@ interface FieldBuilder {
|
|
|
190
198
|
* editor still gets it via `db.get("Variant", id)`.
|
|
191
199
|
*/
|
|
192
200
|
syncOmit(): FieldBuilder;
|
|
201
|
+
/**
|
|
202
|
+
* Cap the value at `n` characters (Unicode code points). String and
|
|
203
|
+
* richtext fields only. See [`FieldDefinition.maxLength`].
|
|
204
|
+
*
|
|
205
|
+
* Example: `bio: field.string().max(280)` stops any client, guests
|
|
206
|
+
* included, from storing a longer bio that every other client would
|
|
207
|
+
* then sync.
|
|
208
|
+
*/
|
|
209
|
+
max(n: number): FieldBuilder;
|
|
193
210
|
/**
|
|
194
211
|
* Mark the field as set-on-insert-only. See [`FieldDefinition.readonly`]
|
|
195
212
|
* for the full semantics.
|
|
@@ -273,6 +290,17 @@ function buildField(def: FieldDefinition): FieldBuilder {
|
|
|
273
290
|
syncOmit() {
|
|
274
291
|
return buildField({ ...def, syncOmit: true });
|
|
275
292
|
},
|
|
293
|
+
max(n: number) {
|
|
294
|
+
if (def.type !== "string" && def.type !== "richtext") {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`.max(n) applies to field.string() and field.richtext(), not ${def.type}`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (!Number.isInteger(n) || n < 1 || n > 0xffffffff) {
|
|
300
|
+
throw new Error(`.max(n): n must be a positive integer, got ${n}`);
|
|
301
|
+
}
|
|
302
|
+
return buildField({ ...def, maxLength: n });
|
|
303
|
+
},
|
|
276
304
|
readonly() {
|
|
277
305
|
return buildField({ ...def, readonly: true });
|
|
278
306
|
},
|
|
@@ -715,6 +743,9 @@ export interface ManifestField {
|
|
|
715
743
|
/** Set when the field is `field.X().syncOmit()` — see
|
|
716
744
|
* [`FieldDefinition.syncOmit`]. Omitted by default. */
|
|
717
745
|
syncOmit?: boolean;
|
|
746
|
+
/** Set by `.max(n)` — see [`FieldDefinition.maxLength`]. Omitted when
|
|
747
|
+
* the field has no limit. */
|
|
748
|
+
maxLength?: number;
|
|
718
749
|
/** Set when the field is `field.X().readonly()` — see
|
|
719
750
|
* [`FieldDefinition.readonly`]. Omitted by default. */
|
|
720
751
|
readonly?: boolean;
|
|
@@ -1240,6 +1271,9 @@ export function entitiesToManifest(
|
|
|
1240
1271
|
if (fb._def.syncOmit) {
|
|
1241
1272
|
f.syncOmit = true;
|
|
1242
1273
|
}
|
|
1274
|
+
if (fb._def.maxLength !== undefined) {
|
|
1275
|
+
f.maxLength = fb._def.maxLength;
|
|
1276
|
+
}
|
|
1243
1277
|
if (fb._def.readonly) {
|
|
1244
1278
|
f.readonly = true;
|
|
1245
1279
|
}
|
|
@@ -2327,6 +2361,9 @@ function agentEntities(): EntityDefinition[] {
|
|
|
2327
2361
|
// Cumulative model↔tool round-trips across every invocation of
|
|
2328
2362
|
// this run. `maxSteps` bounds one invocation, not this.
|
|
2329
2363
|
steps: field.int().optional(),
|
|
2364
|
+
// What server code passed as `context` to ctx.agents.run; the
|
|
2365
|
+
// tools receive it as `run.context`. Written by server code only.
|
|
2366
|
+
context: field.json().optional(),
|
|
2330
2367
|
createdAt: field.datetime(),
|
|
2331
2368
|
updatedAt: field.datetime(),
|
|
2332
2369
|
},
|