@coffer-org/plugin-transit 7.1.0 → 7.2.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/bus_route/index.js +9 -4
- package/dist/index.js +2 -0
- package/dist/schema.js +115 -3013
- package/package.json +3 -3
package/dist/schema.js
CHANGED
|
@@ -21,14 +21,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
-
//#region ../sdk/
|
|
24
|
+
//#region ../sdk/dist/library.js
|
|
25
25
|
function defineLibrary(v) {
|
|
26
26
|
return v;
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
|
-
//#region ../sdk/
|
|
29
|
+
//#region ../sdk/dist/plugin.js
|
|
30
30
|
function definePlugin(p) {
|
|
31
31
|
if (!p.id) throw new Error("[plugin] missing id");
|
|
32
|
+
if (!p.label) throw new Error(`[plugin] ${p.id}: missing label`);
|
|
33
|
+
if (!p.description) throw new Error(`[plugin] ${p.id}: missing description`);
|
|
32
34
|
if (!p.version) console.warn(`[plugin] ${p.id}: missing version`);
|
|
33
35
|
return p;
|
|
34
36
|
}
|
|
@@ -169,7 +171,7 @@ function slugify(input) {
|
|
|
169
171
|
return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
170
172
|
}
|
|
171
173
|
var captureStackTrace = "captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => {};
|
|
172
|
-
function isObject
|
|
174
|
+
function isObject(data) {
|
|
173
175
|
return typeof data === "object" && data !== null && !Array.isArray(data);
|
|
174
176
|
}
|
|
175
177
|
var allowsEval = /* @__PURE__*/ cached(() => {
|
|
@@ -183,12 +185,12 @@ var allowsEval = /* @__PURE__*/ cached(() => {
|
|
|
183
185
|
}
|
|
184
186
|
});
|
|
185
187
|
function isPlainObject(o) {
|
|
186
|
-
if (isObject
|
|
188
|
+
if (isObject(o) === false) return false;
|
|
187
189
|
const ctor = o.constructor;
|
|
188
190
|
if (ctor === void 0) return true;
|
|
189
191
|
if (typeof ctor !== "function") return true;
|
|
190
192
|
const prot = ctor.prototype;
|
|
191
|
-
if (isObject
|
|
193
|
+
if (isObject(prot) === false) return false;
|
|
192
194
|
if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) return false;
|
|
193
195
|
return true;
|
|
194
196
|
}
|
|
@@ -1590,13 +1592,13 @@ var $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => {
|
|
|
1590
1592
|
}
|
|
1591
1593
|
return propValues;
|
|
1592
1594
|
});
|
|
1593
|
-
const isObject = isObject
|
|
1595
|
+
const isObject$2 = isObject;
|
|
1594
1596
|
const catchall = def.catchall;
|
|
1595
1597
|
let value;
|
|
1596
1598
|
inst._zod.parse = (payload, ctx) => {
|
|
1597
1599
|
value ?? (value = _normalized.value);
|
|
1598
1600
|
const input = payload.value;
|
|
1599
|
-
if (!isObject(input)) {
|
|
1601
|
+
if (!isObject$2(input)) {
|
|
1600
1602
|
payload.issues.push({
|
|
1601
1603
|
expected: "object",
|
|
1602
1604
|
code: "invalid_type",
|
|
@@ -1719,7 +1721,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
|
|
|
1719
1721
|
return (payload, ctx) => fn(shape, payload, ctx);
|
|
1720
1722
|
};
|
|
1721
1723
|
let fastpass;
|
|
1722
|
-
const isObject = isObject
|
|
1724
|
+
const isObject$1 = isObject;
|
|
1723
1725
|
const jit = !globalConfig.jitless;
|
|
1724
1726
|
const fastEnabled = jit && allowsEval.value;
|
|
1725
1727
|
const catchall = def.catchall;
|
|
@@ -1727,7 +1729,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
|
|
|
1727
1729
|
inst._zod.parse = (payload, ctx) => {
|
|
1728
1730
|
value ?? (value = _normalized.value);
|
|
1729
1731
|
const input = payload.value;
|
|
1730
|
-
if (!isObject(input)) {
|
|
1732
|
+
if (!isObject$1(input)) {
|
|
1731
1733
|
payload.issues.push({
|
|
1732
1734
|
expected: "object",
|
|
1733
1735
|
code: "invalid_type",
|
|
@@ -2729,7 +2731,7 @@ function initializeContext(params) {
|
|
|
2729
2731
|
external: params?.external ?? void 0
|
|
2730
2732
|
};
|
|
2731
2733
|
}
|
|
2732
|
-
function process
|
|
2734
|
+
function process(schema, ctx, _params = {
|
|
2733
2735
|
path: [],
|
|
2734
2736
|
schemaPath: []
|
|
2735
2737
|
}) {
|
|
@@ -2766,7 +2768,7 @@ function process$1(schema, ctx, _params = {
|
|
|
2766
2768
|
const parent = schema._zod.parent;
|
|
2767
2769
|
if (parent) {
|
|
2768
2770
|
if (!result.ref) result.ref = parent;
|
|
2769
|
-
process
|
|
2771
|
+
process(parent, ctx, params);
|
|
2770
2772
|
ctx.seen.get(parent).isParent = true;
|
|
2771
2773
|
}
|
|
2772
2774
|
}
|
|
@@ -2986,7 +2988,7 @@ var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
|
|
|
2986
2988
|
...params,
|
|
2987
2989
|
processors
|
|
2988
2990
|
});
|
|
2989
|
-
process
|
|
2991
|
+
process(schema, ctx);
|
|
2990
2992
|
extractDefs(ctx, schema);
|
|
2991
2993
|
return finalize(ctx, schema);
|
|
2992
2994
|
};
|
|
@@ -2998,7 +3000,7 @@ var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) =
|
|
|
2998
3000
|
io,
|
|
2999
3001
|
processors
|
|
3000
3002
|
});
|
|
3001
|
-
process
|
|
3003
|
+
process(schema, ctx);
|
|
3002
3004
|
extractDefs(ctx, schema);
|
|
3003
3005
|
return finalize(ctx, schema);
|
|
3004
3006
|
};
|
|
@@ -3078,7 +3080,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
|
|
|
3078
3080
|
if (typeof minimum === "number") json.minItems = minimum;
|
|
3079
3081
|
if (typeof maximum === "number") json.maxItems = maximum;
|
|
3080
3082
|
json.type = "array";
|
|
3081
|
-
json.items = process
|
|
3083
|
+
json.items = process(def.element, ctx, {
|
|
3082
3084
|
...params,
|
|
3083
3085
|
path: [...params.path, "items"]
|
|
3084
3086
|
});
|
|
@@ -3089,7 +3091,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
3089
3091
|
json.type = "object";
|
|
3090
3092
|
json.properties = {};
|
|
3091
3093
|
const shape = def.shape;
|
|
3092
|
-
for (const key in shape) json.properties[key] = process
|
|
3094
|
+
for (const key in shape) json.properties[key] = process(shape[key], ctx, {
|
|
3093
3095
|
...params,
|
|
3094
3096
|
path: [
|
|
3095
3097
|
...params.path,
|
|
@@ -3107,7 +3109,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
3107
3109
|
if (def.catchall?._zod.def.type === "never") json.additionalProperties = false;
|
|
3108
3110
|
else if (!def.catchall) {
|
|
3109
3111
|
if (ctx.io === "output") json.additionalProperties = false;
|
|
3110
|
-
} else if (def.catchall) json.additionalProperties = process
|
|
3112
|
+
} else if (def.catchall) json.additionalProperties = process(def.catchall, ctx, {
|
|
3111
3113
|
...params,
|
|
3112
3114
|
path: [...params.path, "additionalProperties"]
|
|
3113
3115
|
});
|
|
@@ -3115,7 +3117,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
3115
3117
|
var unionProcessor = (schema, ctx, json, params) => {
|
|
3116
3118
|
const def = schema._zod.def;
|
|
3117
3119
|
const isExclusive = def.inclusive === false;
|
|
3118
|
-
const options = def.options.map((x, i) => process
|
|
3120
|
+
const options = def.options.map((x, i) => process(x, ctx, {
|
|
3119
3121
|
...params,
|
|
3120
3122
|
path: [
|
|
3121
3123
|
...params.path,
|
|
@@ -3128,7 +3130,7 @@ var unionProcessor = (schema, ctx, json, params) => {
|
|
|
3128
3130
|
};
|
|
3129
3131
|
var intersectionProcessor = (schema, ctx, json, params) => {
|
|
3130
3132
|
const def = schema._zod.def;
|
|
3131
|
-
const a = process
|
|
3133
|
+
const a = process(def.left, ctx, {
|
|
3132
3134
|
...params,
|
|
3133
3135
|
path: [
|
|
3134
3136
|
...params.path,
|
|
@@ -3136,7 +3138,7 @@ var intersectionProcessor = (schema, ctx, json, params) => {
|
|
|
3136
3138
|
0
|
|
3137
3139
|
]
|
|
3138
3140
|
});
|
|
3139
|
-
const b = process
|
|
3141
|
+
const b = process(def.right, ctx, {
|
|
3140
3142
|
...params,
|
|
3141
3143
|
path: [
|
|
3142
3144
|
...params.path,
|
|
@@ -3149,7 +3151,7 @@ var intersectionProcessor = (schema, ctx, json, params) => {
|
|
|
3149
3151
|
};
|
|
3150
3152
|
var nullableProcessor = (schema, ctx, json, params) => {
|
|
3151
3153
|
const def = schema._zod.def;
|
|
3152
|
-
const inner = process
|
|
3154
|
+
const inner = process(def.innerType, ctx, params);
|
|
3153
3155
|
const seen = ctx.seen.get(schema);
|
|
3154
3156
|
if (ctx.target === "openapi-3.0") {
|
|
3155
3157
|
seen.ref = def.innerType;
|
|
@@ -3158,27 +3160,27 @@ var nullableProcessor = (schema, ctx, json, params) => {
|
|
|
3158
3160
|
};
|
|
3159
3161
|
var nonoptionalProcessor = (schema, ctx, _json, params) => {
|
|
3160
3162
|
const def = schema._zod.def;
|
|
3161
|
-
process
|
|
3163
|
+
process(def.innerType, ctx, params);
|
|
3162
3164
|
const seen = ctx.seen.get(schema);
|
|
3163
3165
|
seen.ref = def.innerType;
|
|
3164
3166
|
};
|
|
3165
3167
|
var defaultProcessor = (schema, ctx, json, params) => {
|
|
3166
3168
|
const def = schema._zod.def;
|
|
3167
|
-
process
|
|
3169
|
+
process(def.innerType, ctx, params);
|
|
3168
3170
|
const seen = ctx.seen.get(schema);
|
|
3169
3171
|
seen.ref = def.innerType;
|
|
3170
3172
|
json.default = JSON.parse(JSON.stringify(def.defaultValue));
|
|
3171
3173
|
};
|
|
3172
3174
|
var prefaultProcessor = (schema, ctx, json, params) => {
|
|
3173
3175
|
const def = schema._zod.def;
|
|
3174
|
-
process
|
|
3176
|
+
process(def.innerType, ctx, params);
|
|
3175
3177
|
const seen = ctx.seen.get(schema);
|
|
3176
3178
|
seen.ref = def.innerType;
|
|
3177
3179
|
if (ctx.io === "input") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));
|
|
3178
3180
|
};
|
|
3179
3181
|
var catchProcessor = (schema, ctx, json, params) => {
|
|
3180
3182
|
const def = schema._zod.def;
|
|
3181
|
-
process
|
|
3183
|
+
process(def.innerType, ctx, params);
|
|
3182
3184
|
const seen = ctx.seen.get(schema);
|
|
3183
3185
|
seen.ref = def.innerType;
|
|
3184
3186
|
let catchValue;
|
|
@@ -3193,20 +3195,20 @@ var pipeProcessor = (schema, ctx, _json, params) => {
|
|
|
3193
3195
|
const def = schema._zod.def;
|
|
3194
3196
|
const inIsTransform = def.in._zod.traits.has("$ZodTransform");
|
|
3195
3197
|
const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
|
|
3196
|
-
process
|
|
3198
|
+
process(innerType, ctx, params);
|
|
3197
3199
|
const seen = ctx.seen.get(schema);
|
|
3198
3200
|
seen.ref = innerType;
|
|
3199
3201
|
};
|
|
3200
3202
|
var readonlyProcessor = (schema, ctx, json, params) => {
|
|
3201
3203
|
const def = schema._zod.def;
|
|
3202
|
-
process
|
|
3204
|
+
process(def.innerType, ctx, params);
|
|
3203
3205
|
const seen = ctx.seen.get(schema);
|
|
3204
3206
|
seen.ref = def.innerType;
|
|
3205
3207
|
json.readOnly = true;
|
|
3206
3208
|
};
|
|
3207
3209
|
var optionalProcessor = (schema, ctx, _json, params) => {
|
|
3208
3210
|
const def = schema._zod.def;
|
|
3209
|
-
process
|
|
3211
|
+
process(def.innerType, ctx, params);
|
|
3210
3212
|
const seen = ctx.seen.get(schema);
|
|
3211
3213
|
seen.ref = def.innerType;
|
|
3212
3214
|
};
|
|
@@ -4067,7 +4069,7 @@ function number(params) {
|
|
|
4067
4069
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
4068
4070
|
}
|
|
4069
4071
|
//#endregion
|
|
4070
|
-
//#region ../sdk/
|
|
4072
|
+
//#region ../sdk/dist/units.js
|
|
4071
4073
|
var UNITS_CURRENCY = [
|
|
4072
4074
|
{
|
|
4073
4075
|
value: "UAH",
|
|
@@ -4371,20 +4373,6 @@ var UNITS_MAP = {
|
|
|
4371
4373
|
label: "core.units.ppm"
|
|
4372
4374
|
}]
|
|
4373
4375
|
};
|
|
4374
|
-
/**
|
|
4375
|
-
* Resolve a unit spec — a named scale or an explicit option list — to its options.
|
|
4376
|
-
*
|
|
4377
|
-
* Throws rather than returning `undefined`, because both ways of reaching `undefined` here
|
|
4378
|
-
* produce a field that CONSTRUCTS and then crashes when someone tries to save through it:
|
|
4379
|
-
* `measured`'s unit role closes over these options and calls `.some(…)` on them inside its
|
|
4380
|
-
* refine, so a missing spec or a typo'd scale name turns a user's save into a
|
|
4381
|
-
* `Cannot read properties of undefined` instead of a validation message. The golden matrix
|
|
4382
|
-
* recorded exactly that for `f.measured({})`.
|
|
4383
|
-
*
|
|
4384
|
-
* A declaration error belongs at declaration time, where the plugin author sees it and the
|
|
4385
|
-
* message can name the scale they meant — the same reason the host-services seam fails at
|
|
4386
|
-
* registration rather than at first render.
|
|
4387
|
-
*/
|
|
4388
4376
|
function resolveUnits(u) {
|
|
4389
4377
|
if (typeof u === "string") {
|
|
4390
4378
|
const known = UNITS_MAP[u];
|
|
@@ -4395,48 +4383,25 @@ function resolveUnits(u) {
|
|
|
4395
4383
|
return u;
|
|
4396
4384
|
}
|
|
4397
4385
|
//#endregion
|
|
4398
|
-
//#region ../sdk/
|
|
4399
|
-
/** Structured message for zod: JSON {code, params}. Decoded by mutate.ts. */
|
|
4386
|
+
//#region ../sdk/dist/fields/validation.js
|
|
4400
4387
|
function vmsg(code, params) {
|
|
4401
4388
|
return JSON.stringify(params ? {
|
|
4402
4389
|
code,
|
|
4403
4390
|
params
|
|
4404
4391
|
} : { code });
|
|
4405
4392
|
}
|
|
4406
|
-
/** v4 error-map: message for a missing value (formerly required_error). */
|
|
4407
4393
|
function reqErr(code = "required") {
|
|
4408
4394
|
return { error: (iss) => iss.input === void 0 ? vmsg(code) : void 0 };
|
|
4409
4395
|
}
|
|
4410
|
-
/** v4 error-map: message for an invalid type (formerly invalid_type_error). */
|
|
4411
4396
|
function typeErr(code = "invalid_type") {
|
|
4412
4397
|
return { error: (iss) => iss.code === "invalid_type" ? vmsg(code) : void 0 };
|
|
4413
4398
|
}
|
|
4414
|
-
/** v4 error-map: required + invalid_type together (formerly required_error + invalid_type_error). */
|
|
4415
4399
|
function reqTypeErr() {
|
|
4416
4400
|
return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
|
|
4417
4401
|
}
|
|
4418
|
-
/**
|
|
4419
|
-
* A string schema whose CONTENT checks only speak once the value is known to be a string.
|
|
4420
|
-
*
|
|
4421
|
-
* zod's length checks are not type-guarded: `min`/`max` read `input.length`, and an ARRAY has
|
|
4422
|
-
* one. So `z.string().min(1)` answers `[]` with `invalid_type` AND `too_small` — two messages
|
|
4423
|
-
* about a single wrongness, both of which reach the user, since `mutate.ts` and `extend-io.ts`
|
|
4424
|
-
* turn every issue into its own `ValidationError`. (A number answers with `invalid_type`
|
|
4425
|
-
* alone, having no `length` to read — so the redundancy was array-shaped and invisible until
|
|
4426
|
-
* the golden matrix started recording the whole issue list instead of `issues[0]`.)
|
|
4427
|
-
*
|
|
4428
|
-
* Piping puts the type question first and alone: `add` never runs on a non-string. Pass the
|
|
4429
|
-
* content checks as a builder rather than chaining them onto the result, because a `ZodPipe`
|
|
4430
|
-
* has no `.min`/`.max`/`.regex` to chain.
|
|
4431
|
-
*/
|
|
4432
4402
|
function stringContent(add) {
|
|
4433
4403
|
return string$1(reqTypeErr()).pipe(add(string$1()));
|
|
4434
4404
|
}
|
|
4435
|
-
/** Parse a JSON string, else return the native value (object/array) as-is.
|
|
4436
|
-
* Tolerant input for native form-state AND legacy JSON-string payloads.
|
|
4437
|
-
* A string that fails JSON.parse is returned unchanged — callers detect
|
|
4438
|
-
* parse failure via `typeof result === 'string'` (inners here are object/array,
|
|
4439
|
-
* never a bare string). */
|
|
4440
4405
|
function jsonValue(raw) {
|
|
4441
4406
|
if (typeof raw === "string") try {
|
|
4442
4407
|
return JSON.parse(raw);
|
|
@@ -4445,12 +4410,6 @@ function jsonValue(raw) {
|
|
|
4445
4410
|
}
|
|
4446
4411
|
return raw;
|
|
4447
4412
|
}
|
|
4448
|
-
/**
|
|
4449
|
-
* Factory for fields that store JSON and validate it with a nested zod schema.
|
|
4450
|
-
* Accepts a native object/array (native form state) OR a JSON string (legacy).
|
|
4451
|
-
* Single parse through jsonValue → inner.safeParse → issue `code`; an unparseable
|
|
4452
|
-
* string remains a string → code 'json'.
|
|
4453
|
-
*/
|
|
4454
4413
|
function jsonRefined(inner, code) {
|
|
4455
4414
|
return unknown().superRefine((raw, ctx) => {
|
|
4456
4415
|
const parsed = jsonValue(raw);
|
|
@@ -4467,35 +4426,11 @@ function jsonRefined(inner, code) {
|
|
|
4467
4426
|
});
|
|
4468
4427
|
});
|
|
4469
4428
|
}
|
|
4470
|
-
function optionalize(schema
|
|
4471
|
-
|
|
4472
|
-
const pre = (v) => v === "" || v === null ? void 0 : v;
|
|
4473
|
-
if (!req) return preprocess(pre, schema.optional());
|
|
4474
|
-
return preprocess(pre, unknown().superRefine((v, ctx) => {
|
|
4475
|
-
if (v === void 0) ctx.addIssue({
|
|
4476
|
-
code: ZodIssueCode.custom,
|
|
4477
|
-
message: vmsg("required")
|
|
4478
|
-
});
|
|
4479
|
-
}).pipe(schema));
|
|
4429
|
+
function optionalize(schema) {
|
|
4430
|
+
return preprocess((v) => v === "" || v === null ? void 0 : v, schema.optional());
|
|
4480
4431
|
}
|
|
4481
4432
|
//#endregion
|
|
4482
|
-
//#region ../sdk/
|
|
4483
|
-
/**
|
|
4484
|
-
* Field META modifiers — the shared machinery a built field (FieldMeta) passes through
|
|
4485
|
-
* regardless of its type: value options (`applyOptions`/`bindOptionsZod`), the `multiple`
|
|
4486
|
-
* array wrap (`applyMultiple`), and the final node-or-bare-FieldMeta shape
|
|
4487
|
-
* (`wrapKey`, EVERY factory's last step).
|
|
4488
|
-
*
|
|
4489
|
-
* A LEAF module, the same shape as `fields/validation.ts`/`fields/normalize.ts`: it
|
|
4490
|
-
* borrows the FieldMeta/LayoutNode/FieldClient/ColumnType/OptionItem SHAPES from
|
|
4491
|
-
* `../fields.ts` as TYPE-ONLY imports (erased at compile time — no runtime edge), but has
|
|
4492
|
-
* NO runtime dependency on `../fields.ts` itself. This is what lets
|
|
4493
|
-
* `materialize/pipeline.ts` import `applyMultiple`/`wrapKey` from here instead of from
|
|
4494
|
-
* `fields.ts`, which in turn is what lets `fields.ts` import `materialize` FROM
|
|
4495
|
-
* `materialize/pipeline.ts` without closing a cycle (`fields.ts` → `pipeline.ts` →
|
|
4496
|
-
* `fields/meta.ts`, nothing pointing back). `fields.ts` re-exports every symbol here, so
|
|
4497
|
-
* no external import path (`@coffer-org/sdk/fields`) changes.
|
|
4498
|
-
*/
|
|
4433
|
+
//#region ../sdk/dist/fields/meta.js
|
|
4499
4434
|
function safeJsonParse(s) {
|
|
4500
4435
|
try {
|
|
4501
4436
|
return JSON.parse(s);
|
|
@@ -4503,14 +4438,6 @@ function safeJsonParse(s) {
|
|
|
4503
4438
|
return;
|
|
4504
4439
|
}
|
|
4505
4440
|
}
|
|
4506
|
-
/**
|
|
4507
|
-
* Wraps a field into a JSON array of values (storage: TEXT). The inner zod is `base.zod`.
|
|
4508
|
-
* Modifier order: base → applyMultiple.
|
|
4509
|
-
*
|
|
4510
|
-
* Exception: kind 'image'/'media' handle multiple themselves in the renderer (gallery) —
|
|
4511
|
-
* the MultipleField* wrappers on the web skip them.
|
|
4512
|
-
*/
|
|
4513
|
-
/** Zod "JSON array of inner values" — used by applyMultiple. */
|
|
4514
4441
|
function multipleZod(inner) {
|
|
4515
4442
|
return unknown().superRefine((raw, ctx) => {
|
|
4516
4443
|
let arr;
|
|
@@ -4558,18 +4485,15 @@ function applyMultiple(base, multiple) {
|
|
|
4558
4485
|
multiple: true
|
|
4559
4486
|
},
|
|
4560
4487
|
json: true,
|
|
4561
|
-
zod: optionalize(s
|
|
4488
|
+
zod: optionalize(s)
|
|
4562
4489
|
};
|
|
4563
4490
|
}
|
|
4564
|
-
/** Inline option entry → OptionItem (plain string = value and label at once). */
|
|
4565
4491
|
function toOptionItem(o) {
|
|
4566
4492
|
return typeof o === "string" ? {
|
|
4567
4493
|
value: o,
|
|
4568
4494
|
title: o
|
|
4569
4495
|
} : o;
|
|
4570
4496
|
}
|
|
4571
|
-
/** Canonical comparison form. Both the input and the option value pass through it, so a
|
|
4572
|
-
* numeric field compares `100` against the option spelled `'100'`. */
|
|
4573
4497
|
function optionKey(column, v) {
|
|
4574
4498
|
if (column === "integer" || column === "real") {
|
|
4575
4499
|
const n = Number(v);
|
|
@@ -4577,12 +4501,6 @@ function optionKey(column, v) {
|
|
|
4577
4501
|
}
|
|
4578
4502
|
return String(v ?? "");
|
|
4579
4503
|
}
|
|
4580
|
-
/**
|
|
4581
|
-
* Membership check LAYERED OVER the field's own schema, never replacing it. Replacing it
|
|
4582
|
-
* with `z.enum` — which is what select used to do — works only over a bare string: over an
|
|
4583
|
-
* int it would destroy the int-ness, the coercion and the min/max, and over an ip it would
|
|
4584
|
-
* destroy the structure. Layering keeps every type's own error code and adds one.
|
|
4585
|
-
*/
|
|
4586
4504
|
function gateZod(inner, gate, multiple, column) {
|
|
4587
4505
|
return inner.superRefine((v, ctx) => {
|
|
4588
4506
|
if (gate.values === null || v === void 0 || v === null || v === "") return;
|
|
@@ -4594,11 +4512,6 @@ function gateZod(inner, gate, multiple, column) {
|
|
|
4594
4512
|
});
|
|
4595
4513
|
});
|
|
4596
4514
|
}
|
|
4597
|
-
/**
|
|
4598
|
-
* Attaches `options`/`strict` to any field. Called from `wrapKey`, which EVERY factory
|
|
4599
|
-
* funnels through — core and plugin-defined alike — so a new field type gets value options
|
|
4600
|
-
* without writing a line for them.
|
|
4601
|
-
*/
|
|
4602
4515
|
function applyOptions(base, o) {
|
|
4603
4516
|
if (base.optionGate) return base;
|
|
4604
4517
|
if (base.relation || base.parts || base.options) return base;
|
|
@@ -4627,22 +4540,10 @@ function applyOptions(base, o) {
|
|
|
4627
4540
|
}
|
|
4628
4541
|
return m;
|
|
4629
4542
|
}
|
|
4630
|
-
/**
|
|
4631
|
-
* Fills (or refills) a strict field's closed set. Called by composeRegistry once the full
|
|
4632
|
-
* option set is known — inline options, plugin contributions and the named source merged.
|
|
4633
|
-
* A non-strict field has no gate and is left alone.
|
|
4634
|
-
*/
|
|
4635
4543
|
function bindOptionsZod(field, values) {
|
|
4636
4544
|
if (!field.optionGate || !values.length) return;
|
|
4637
4545
|
field.optionGate.values = new Set(values.map((v) => optionKey(field.column, v)));
|
|
4638
4546
|
}
|
|
4639
|
-
/**
|
|
4640
|
-
* Wraps a FieldMeta into a node. `key` is the name the PARENT gave this child
|
|
4641
|
-
* (materialize/pipeline.ts) — an argument, not an option a factory reads out of its own
|
|
4642
|
-
* opts, which is why it comes first. Absent means the element was never declared under a
|
|
4643
|
-
* name: it becomes a keyless value node when it carries a `value`, and a bare FieldMeta
|
|
4644
|
-
* otherwise.
|
|
4645
|
-
*/
|
|
4646
4547
|
function wrapKey(key, opts, meta) {
|
|
4647
4548
|
let m = applyOptions(meta, opts);
|
|
4648
4549
|
if (opts.noEditControl) m = {
|
|
@@ -4673,13 +4574,6 @@ function wrapKey(key, opts, meta) {
|
|
|
4673
4574
|
noLabel: true
|
|
4674
4575
|
}
|
|
4675
4576
|
};
|
|
4676
|
-
if (opts.role) m = {
|
|
4677
|
-
...m,
|
|
4678
|
-
hints: {
|
|
4679
|
-
...m.hints,
|
|
4680
|
-
role: opts.role
|
|
4681
|
-
}
|
|
4682
|
-
};
|
|
4683
4577
|
if (opts.editor) m = {
|
|
4684
4578
|
...m,
|
|
4685
4579
|
hints: {
|
|
@@ -4769,7 +4663,7 @@ function wrapKey(key, opts, meta) {
|
|
|
4769
4663
|
return m;
|
|
4770
4664
|
}
|
|
4771
4665
|
//#endregion
|
|
4772
|
-
//#region ../sdk/
|
|
4666
|
+
//#region ../sdk/dist/fields/normalize.js
|
|
4773
4667
|
function normalizeOpts(rawIn) {
|
|
4774
4668
|
const raw = rawIn;
|
|
4775
4669
|
if (raw["view"] !== void 0) throw new Error("[normalizeOpts] `view` is gone — presentational options live in `ui: {}` (see CLAUDE.md § field author options and the field-types skill).");
|
|
@@ -4810,37 +4704,14 @@ function normalizeOpts(rawIn) {
|
|
|
4810
4704
|
faces: v.faces,
|
|
4811
4705
|
emphasis: v.emphasis,
|
|
4812
4706
|
noLabel: v.noLabel,
|
|
4813
|
-
role: v.role,
|
|
4814
4707
|
editor: v.editor,
|
|
4815
4708
|
activate: v.activate
|
|
4816
4709
|
};
|
|
4817
4710
|
return Object.fromEntries(Object.entries(result).filter(([, val]) => val !== void 0));
|
|
4818
4711
|
}
|
|
4819
4712
|
//#endregion
|
|
4820
|
-
//#region ../sdk/
|
|
4821
|
-
/**
|
|
4822
|
-
* PHASE 1 — declaration. A factory returns this and nothing else: plain data, no zod,
|
|
4823
|
-
* no column, no widget. A parent may rewrite ANY property of a child's declaration
|
|
4824
|
-
* before phase 2 builds it (see materialize/pipeline.ts).
|
|
4825
|
-
*/
|
|
4713
|
+
//#region ../sdk/dist/materialize/decl.js
|
|
4826
4714
|
var BRAND = "__fieldDecl";
|
|
4827
|
-
/**
|
|
4828
|
-
* `opts.fields` vs `decl.fields` — the same author input reachable two ways, and only one of
|
|
4829
|
-
* them is ever safe to read as BUILT children:
|
|
4830
|
-
*
|
|
4831
|
-
* - `opts.fields` is the RAW author input, unpacked by `normalizeOpts`'s spread (it does
|
|
4832
|
-
* not know or care that `fields` is special) — never rewritten, never materialized. A
|
|
4833
|
-
* composite that computes its OWN roles from what the author passed (`check`'s content
|
|
4834
|
-
* fields) reads it from here, at declare() time, exactly as the author wrote it — decls
|
|
4835
|
-
* and all.
|
|
4836
|
-
* - `decl.fields` is what `declare()` copies from that same raw input for the tree walk
|
|
4837
|
-
* (`materialize/pipeline.ts`) and for `resolveParts` to find. Once a parent has been
|
|
4838
|
-
* materialized, this is where its BUILT children live.
|
|
4839
|
-
*
|
|
4840
|
-
* A consumer that wants built children — not raw author input — reads the materialized tree
|
|
4841
|
-
* (`materialize()`/`materializeTree()`'s output), never `decl.opts.fields`: that stays
|
|
4842
|
-
* exactly what the author passed, decls included, for as long as the decl is unbuilt.
|
|
4843
|
-
*/
|
|
4844
4715
|
function declare(factory, raw) {
|
|
4845
4716
|
const opts = normalizeOpts(raw);
|
|
4846
4717
|
const spec = raw;
|
|
@@ -4855,9 +4726,7 @@ function isDecl(v) {
|
|
|
4855
4726
|
return typeof v === "object" && v !== null && BRAND in v;
|
|
4856
4727
|
}
|
|
4857
4728
|
//#endregion
|
|
4858
|
-
//#region ../sdk/
|
|
4859
|
-
/** True for a type registered through `roles`/`roleZod`/`structureCode` (materialize/composite.ts)
|
|
4860
|
-
* rather than a plain `build()` — read generically, never by factory name. */
|
|
4729
|
+
//#region ../sdk/dist/materialize/registry.js
|
|
4861
4730
|
function isComposite(type) {
|
|
4862
4731
|
return "roles" in type;
|
|
4863
4732
|
}
|
|
@@ -4871,12 +4740,6 @@ function typeOf(factory) {
|
|
|
4871
4740
|
if (!def) throw new Error(`[types] unknown factory '${factory}'`);
|
|
4872
4741
|
return def;
|
|
4873
4742
|
}
|
|
4874
|
-
/**
|
|
4875
|
-
* `presetOpts` merged as DEFAULTS under the caller's own opts (`{ ...presetOpts, ...o }`) —
|
|
4876
|
-
* an author's own value always wins. `config` is merged the same way one level deep, so a
|
|
4877
|
-
* preset's own default (`weight`'s `config.min: 1`) survives an author setting a DIFFERENT
|
|
4878
|
-
* config key (`rules: { max: 5000 }`) without the preset needing to repeat it.
|
|
4879
|
-
*/
|
|
4880
4743
|
function mergePresetOpts(presetOpts, o) {
|
|
4881
4744
|
const config = presetOpts.config || o.config ? {
|
|
4882
4745
|
...presetOpts.config,
|
|
@@ -4888,37 +4751,6 @@ function mergePresetOpts(presetOpts, o) {
|
|
|
4888
4751
|
...config ? { config } : {}
|
|
4889
4752
|
};
|
|
4890
4753
|
}
|
|
4891
|
-
/**
|
|
4892
|
-
* Registers a PRESET: a type that is its base type's `TypeDef` with fixed/default opts
|
|
4893
|
-
* merged in, and — where it differs — its own `kind`/`widget`/`build`. This is what lets
|
|
4894
|
-
* `field-presets.ts` express "a preset is its base type plus these opts" as DATA instead of
|
|
4895
|
-
* copy-pasting normalizeOpts/optionalize/applyMultiple/wrapKey into a hand-written body:
|
|
4896
|
-
* `kind`/`prim`/`widget`/`selfManages` default to the base type's own — a
|
|
4897
|
-
* preset differs from its base in BEHAVIOUR, not in what column/prim family it belongs to; a
|
|
4898
|
-
* change that DOES need a different prim/column is not a preset, it is a new type — and
|
|
4899
|
-
* `over` replaces exactly the properties a given preset differs in.
|
|
4900
|
-
*
|
|
4901
|
-
* `presetOpts` are DEFAULTS the caller's own opts can still override (see `mergePresetOpts`
|
|
4902
|
-
* above). A preset that must instead FORCE a value regardless of the caller (`country`'s
|
|
4903
|
-
* `options: 'countries'`, `tags`'s `multiple: true`) is not this mechanism — those forward
|
|
4904
|
-
* directly to the base factory with the forced opts spread AFTER the caller's own
|
|
4905
|
-
* (`string({ ...raw, options: 'countries' })`), the pattern `country`/`currency`/`percent`/
|
|
4906
|
-
* `year`/`tags` already use, none of which needs its own registry entry: their `factory` IS
|
|
4907
|
-
* the base's, unchanged.
|
|
4908
|
-
*
|
|
4909
|
-
* `over.build`, when given, replaces the base type's `build()` entirely for THIS preset —
|
|
4910
|
-
* still called with the merged opts, so it may itself delegate to the base's own `build()`
|
|
4911
|
-
* and layer extra hints on top (`snippet` adds `hints.language` over `text`'s own build).
|
|
4912
|
-
*
|
|
4913
|
-
* Resolves `base` LAZILY (on first actual read, memoized), never at the moment
|
|
4914
|
-
* `registerPreset` itself is called: `field-presets.ts` calls it at MODULE TOP LEVEL, inside
|
|
4915
|
-
* the same circular import (`fields.ts` ⇄ `field-presets.ts`) its own header note already
|
|
4916
|
-
* documents — depending on which module the loader entered first, the base's own
|
|
4917
|
-
* `registerType` call (in `fields.ts`) may not have run yet. `text`/`i18n`/`smallText` in
|
|
4918
|
-
* fields.ts sidestep the same trap by calling `typeOf('string')` only from inside their
|
|
4919
|
-
* `build()`, never at their own `registerType()` call site; a preset's `kind`/`prim`/
|
|
4920
|
-
* `widget`/`selfManages` need the identical deferral since they too default from the base.
|
|
4921
|
-
*/
|
|
4922
4754
|
function registerPreset(factory, base, presetOpts, over = {}) {
|
|
4923
4755
|
let resolved;
|
|
4924
4756
|
const baseType = () => {
|
|
@@ -4945,44 +4777,17 @@ function registerPreset(factory, base, presetOpts, over = {}) {
|
|
|
4945
4777
|
});
|
|
4946
4778
|
}
|
|
4947
4779
|
//#endregion
|
|
4948
|
-
//#region ../sdk/
|
|
4949
|
-
/**
|
|
4950
|
-
* ONE composite transform. It was written six times (geo, illustrated, measured, range,
|
|
4951
|
-
* dimensions, check(single)) and differed only in the validation code and the per-role
|
|
4952
|
-
* default zod.
|
|
4953
|
-
*
|
|
4954
|
-
* raw → jsonValue → absent by `absenceRoles`? (required → 'required')
|
|
4955
|
-
* → carries nothing at all? (pass through, there is nothing to judge)
|
|
4956
|
-
* → rowSchema (partsRowShape) → stripServerOwnedParts → optional per-type refine
|
|
4957
|
-
*
|
|
4958
|
-
* A `CompositeTypeDef` names ONLY what its own type contributes: its roles, the default
|
|
4959
|
-
* zod per role, and how its structure failure reports. Everything else — the empty
|
|
4960
|
-
* check, the JSON tolerance, the row parse, stripping server-owned parts — lives here
|
|
4961
|
-
* exactly once.
|
|
4962
|
-
*/
|
|
4780
|
+
//#region ../sdk/dist/materialize/composite.js
|
|
4963
4781
|
function buildComposite(def, opts, parts) {
|
|
4964
|
-
const
|
|
4965
|
-
const roleZod = def.roleZod(opts, parts);
|
|
4966
|
-
const rowSchema = object(partsRowShape(roleZod, parts, required === true ? "strict" : "null"));
|
|
4967
|
-
const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts, "null-or-missing")) : rowSchema;
|
|
4782
|
+
const rowSchema = object(partsRowShape(def.roleZod(opts, parts), parts));
|
|
4968
4783
|
const roleKeys = (which) => parts.filter((p) => p.mode === "stored" && (which?.(p) ?? true)).map(partValueKey);
|
|
4969
|
-
const
|
|
4970
|
-
const clientKeys = def.absenceRoles ? roleKeys() : void 0;
|
|
4784
|
+
const clientKeys = def.acceptsEmptyRow ? roleKeys() : void 0;
|
|
4971
4785
|
const zod = unknown().transform((raw, ctx) => {
|
|
4972
4786
|
const parsed = jsonValue(raw);
|
|
4973
4787
|
const p = parsed;
|
|
4974
4788
|
const isRow = p == null || typeof p === "object" && !Array.isArray(p);
|
|
4975
|
-
/** Every one of `keys` empty in this value — `null`/absent alike. */
|
|
4976
4789
|
const allEmpty = (keys) => isRow && (p == null || keys.length > 0 && keys.every((k) => p[k] == null));
|
|
4977
|
-
|
|
4978
|
-
if (absenceKeys && clientKeys) {
|
|
4979
|
-
absent = allEmpty(absenceKeys);
|
|
4980
|
-
if (absent && required) ctx.addIssue({
|
|
4981
|
-
code: ZodIssueCode.custom,
|
|
4982
|
-
message: vmsg("required")
|
|
4983
|
-
});
|
|
4984
|
-
if (allEmpty(clientKeys)) return raw;
|
|
4985
|
-
}
|
|
4790
|
+
if (clientKeys && allEmpty(clientKeys)) return raw;
|
|
4986
4791
|
if (typeof parsed === "string") {
|
|
4987
4792
|
ctx.addIssue({
|
|
4988
4793
|
code: ZodIssueCode.custom,
|
|
@@ -4990,7 +4795,7 @@ function buildComposite(def, opts, parts) {
|
|
|
4990
4795
|
});
|
|
4991
4796
|
return NEVER;
|
|
4992
4797
|
}
|
|
4993
|
-
const r =
|
|
4798
|
+
const r = rowSchema.safeParse(parsed);
|
|
4994
4799
|
if (!r.success) {
|
|
4995
4800
|
const message = typeof def.structureCode === "function" ? def.structureCode(r.error) : vmsg(def.structureCode);
|
|
4996
4801
|
ctx.addIssue({
|
|
@@ -5015,27 +4820,7 @@ function buildComposite(def, opts, parts) {
|
|
|
5015
4820
|
};
|
|
5016
4821
|
}
|
|
5017
4822
|
//#endregion
|
|
5018
|
-
//#region ../sdk/
|
|
5019
|
-
/**
|
|
5020
|
-
* PHASE 2 — materialization. The six steps every field goes through, in one place:
|
|
5021
|
-
* they used to be copied into 40-odd factory bodies (43 normalizeOpts heads,
|
|
5022
|
-
* 38 wrapKey tails, 37 optionalize calls).
|
|
5023
|
-
*
|
|
5024
|
-
* Composites (roles, the shared structure transform) arrive in materialize/composite.ts;
|
|
5025
|
-
* this file resolves a composite's parts and picks the right build path, but the
|
|
5026
|
-
* transform itself lives there, exactly once.
|
|
5027
|
-
*/
|
|
5028
|
-
/**
|
|
5029
|
-
* `name` is the property this declaration was declared under in its parent's map — the ONLY
|
|
5030
|
-
* source of a key a declared field ever has (a factory has no `key` option to read one from)
|
|
5031
|
-
* and it is threaded straight through to `wrapKey`, which keys the result whenever `name` is
|
|
5032
|
-
* defined. Absent (a top-level `materialize()` call on a standalone declaration) the result
|
|
5033
|
-
* is keyless: a value node if it carries a `value`, a bare FieldMeta otherwise.
|
|
5034
|
-
*
|
|
5035
|
-
* This function does not itself stamp the result's `name` (the ADDRESS, `LayoutNode.name` —
|
|
5036
|
-
* see that type's own doc comment) — its caller, `materializeEl`, does that for every branch,
|
|
5037
|
-
* including this one.
|
|
5038
|
-
*/
|
|
4823
|
+
//#region ../sdk/dist/materialize/pipeline.js
|
|
5039
4824
|
function materialize(decl, name) {
|
|
5040
4825
|
const opts = decl.opts;
|
|
5041
4826
|
const type = typeOf(decl.factory);
|
|
@@ -5043,12 +4828,10 @@ function materialize(decl, name) {
|
|
|
5043
4828
|
if (isComposite(type) && (opts.multiple ?? false) && parts.length > 0 && !type.selfManages?.has("multiple")) return collectionGroup(name, opts, parts, type.collectionView);
|
|
5044
4829
|
const built = isComposite(type) ? buildComposite(type, opts, parts) : type.build(opts, parts, name);
|
|
5045
4830
|
const selfOptional = type.selfManages?.has("optional") ?? false;
|
|
5046
|
-
const required = selfOptional ? false : opts.required ?? false;
|
|
5047
4831
|
const base = {
|
|
5048
4832
|
kind: type.kind,
|
|
5049
4833
|
label: opts.label ?? "",
|
|
5050
4834
|
...opts.agent !== void 0 && { agent: opts.agent },
|
|
5051
|
-
required,
|
|
5052
4835
|
prim: type.prim,
|
|
5053
4836
|
column: built.column,
|
|
5054
4837
|
hints: built.hints ?? {},
|
|
@@ -5059,26 +4842,10 @@ function materialize(decl, name) {
|
|
|
5059
4842
|
columns: built.columns,
|
|
5060
4843
|
parts
|
|
5061
4844
|
},
|
|
5062
|
-
zod: selfOptional ? built.zod : optionalize(built.zod
|
|
4845
|
+
zod: selfOptional ? built.zod : optionalize(built.zod)
|
|
5063
4846
|
};
|
|
5064
4847
|
return wrapKey(name, opts, type.selfManages?.has("multiple") ? base : applyMultiple(base, opts.multiple ?? false));
|
|
5065
4848
|
}
|
|
5066
|
-
/**
|
|
5067
|
-
* The shared storage transform behind a composite's `multiple` form: a collection group
|
|
5068
|
-
* (child table) whose row fields ARE the given parts, under their role names — the exact
|
|
5069
|
-
* `ContainerNode` shape `fields.ts`'s own `group({key,label,multiple:true,required,fields,view})`
|
|
5070
|
-
* would build for this call pattern (no icon, no `rules`, no `value` — none of the three
|
|
5071
|
-
* former call sites ever passed them), constructed directly rather than through `group()`
|
|
5072
|
-
* itself to avoid a runtime edge back into fields.ts (see this module's own header note).
|
|
5073
|
-
*
|
|
5074
|
-
* Reached automatically, by the storage rule in `materialize()` above, for every composite
|
|
5075
|
-
* registered through `registerType`/`CompositeTypeDef` — `illustrated`, `check` and
|
|
5076
|
-
* `period` among them. `check`'s `roles(opts)` already returns checkbox-first order for
|
|
5077
|
-
* `multiple` and content-first for `single`, and `period`'s `roles(opts)` returns the same
|
|
5078
|
-
* from/until order regardless of `multiple`, so the generic path needs no special case for
|
|
5079
|
-
* either — the row-field ORDER is each type's own declared data (its `roles`), not
|
|
5080
|
-
* something this helper infers.
|
|
5081
|
-
*/
|
|
5082
4849
|
function collectionGroup(key, opts, parts, collectionView) {
|
|
5083
4850
|
const rowFields = parts.map((p) => ({
|
|
5084
4851
|
...wrapKey(p.role, p.mode === "computedStored" ? { value: p.value } : {}, p.meta),
|
|
@@ -5089,7 +4856,6 @@ function collectionGroup(key, opts, parts, collectionView) {
|
|
|
5089
4856
|
key,
|
|
5090
4857
|
scope: "nest",
|
|
5091
4858
|
multiple: true,
|
|
5092
|
-
required: opts.required ?? false,
|
|
5093
4859
|
label: opts.label ?? key,
|
|
5094
4860
|
...opts.agent !== void 0 && { agent: opts.agent },
|
|
5095
4861
|
display: "wrap",
|
|
@@ -5097,22 +4863,6 @@ function collectionGroup(key, opts, parts, collectionView) {
|
|
|
5097
4863
|
fields: rowFields
|
|
5098
4864
|
};
|
|
5099
4865
|
}
|
|
5100
|
-
/**
|
|
5101
|
-
* One tree node, named by the property it was declared under. Every returned node — every
|
|
5102
|
-
* branch below — carries that name (`name`, the ADDRESS a parent reaches it by) once `name`
|
|
5103
|
-
* is defined; only the STORAGE name (`key`) is conditional.
|
|
5104
|
-
*
|
|
5105
|
-
* A DECLARATION takes that name as both its `name` and its `key` (`materialize` → `wrapKey`
|
|
5106
|
-
* always keys a named declaration). An already-built element takes `key` too, but only if it
|
|
5107
|
-
* OWNS A SCOPE and has not been named yet: a `ContainerNode` that declares `scope: 'nest'`
|
|
5108
|
-
* owns a column prefix or a child table, and that scope has to be named — `group()`/`row()`/
|
|
5109
|
-
* `keyed()` no longer take a key of their own, so the parent's property name is where it
|
|
5110
|
-
* comes from. Everything else — a `'hoist'` group (an editorial block, a sheet row: it
|
|
5111
|
-
* arranges children inside the parent's scope and owns none), a node already carrying its
|
|
5112
|
-
* key, a keyless value node, a pseudo-element — owns no scope, so it keeps whatever `key` it
|
|
5113
|
-
* already had (usually none), but still gets `name` stamped: that is the whole point of this
|
|
5114
|
-
* task — a `'hoist'` child's declared property name used to be discarded here entirely.
|
|
5115
|
-
*/
|
|
5116
4866
|
function materializeEl(el, name) {
|
|
5117
4867
|
if (isDecl(el)) {
|
|
5118
4868
|
const built = materialize(el, name);
|
|
@@ -5138,61 +4888,25 @@ function materializeEl(el, name) {
|
|
|
5138
4888
|
name
|
|
5139
4889
|
} : el;
|
|
5140
4890
|
}
|
|
5141
|
-
/**
|
|
5142
|
-
* A built group that owns a scope but has not been given its storage key yet — i.e. it HAS
|
|
5143
|
-
* children (`fields`, the same question `hasChildren` in `../fields.ts` asks) and a `'nest'`
|
|
5144
|
-
* scope, but no key. Checked structurally rather than by importing `hasChildren`: this file
|
|
5145
|
-
* has no RUNTIME edge back into `fields.ts` (see the header note) — `Array.isArray` is the
|
|
5146
|
-
* same HAS-question, asked locally to keep that boundary.
|
|
5147
|
-
*/
|
|
5148
4891
|
function isNamelessScope(el) {
|
|
5149
4892
|
const g = el;
|
|
5150
4893
|
return typeof g === "object" && g !== null && Array.isArray(g.fields) && g.scope === "nest" && g.key == null;
|
|
5151
4894
|
}
|
|
5152
|
-
/**
|
|
5153
|
-
* A bare `FieldMeta`: it HAS none of a node's own marks yet — no `el` (so not a static value or
|
|
5154
|
-
* a pseudo-element — those are keyless ON PURPOSE and keep only an address) and no `key` (so
|
|
5155
|
-
* not a named field) — but it DOES carry a built field's own marks (`kind`, `zod`). This is a
|
|
5156
|
-
* declaration-stage shape, before it becomes a node at all, so it asks about its own
|
|
5157
|
-
* properties directly rather than through `hasValue`/`hasChildren` (which read `.type`/
|
|
5158
|
-
* `.fields`, not `.kind`/`.zod`).
|
|
5159
|
-
*/
|
|
5160
4895
|
function isNamelessField(el) {
|
|
5161
4896
|
const m = el;
|
|
5162
4897
|
return typeof m === "object" && m !== null && !("el" in m) && !("key" in m) && typeof m.kind === "string" && m.zod !== void 0;
|
|
5163
4898
|
}
|
|
5164
|
-
/** `group()` falls back to the key for a missing label; with the key arriving here instead,
|
|
5165
|
-
* that fallback has to be applied here too, or a nameless scope would keep an empty one. */
|
|
5166
4899
|
function labelOf(el, name) {
|
|
5167
4900
|
const g = el;
|
|
5168
4901
|
return g.label === void 0 || g.label === "" ? name : g.label;
|
|
5169
4902
|
}
|
|
5170
4903
|
var NUMERIC_LIKE$1 = /^\d+$/;
|
|
5171
|
-
/**
|
|
5172
|
-
* A LayoutEl tree: declarations become built elements, everything else passes through.
|
|
5173
|
-
* ONE input shape — a NAMED MAP, whose property name becomes the child's key. A
|
|
5174
|
-
* numeric-like name throws: object iteration would reorder such keys, and declaration
|
|
5175
|
-
* order IS the field order.
|
|
5176
|
-
*/
|
|
5177
4904
|
function materializeTree(els) {
|
|
5178
4905
|
return Object.entries(els).map(([name, el]) => {
|
|
5179
4906
|
if (NUMERIC_LIKE$1.test(name)) throw new Error(`fields: numeric-like child name '${name}' (object iteration would reorder the children)`);
|
|
5180
4907
|
return materializeEl(el, name);
|
|
5181
4908
|
});
|
|
5182
4909
|
}
|
|
5183
|
-
/**
|
|
5184
|
-
* A ShelfDef/ExtendDef/SettingsDef-shaped object: materializes its `fields` tree, every
|
|
5185
|
-
* other property carried through unchanged. `defineShelf`/`defineExtend`/`defineSettings`
|
|
5186
|
-
* (and `composeRegistry`'s defensive pass over a hand-rolled manifest) all call this instead
|
|
5187
|
-
* of repeating `{ ...def, fields: materializeTree(def.fields) as LayoutEl[] }` — the
|
|
5188
|
-
* copy-paste this refactor exists to delete, one level up from the factory bodies.
|
|
5189
|
-
*
|
|
5190
|
-
* `T`'s own `fields` may be the author-facing `LayoutInput` (a `ShelfDefInput`/
|
|
5191
|
-
* `ExtendDefInput`/`SettingsDefInput`) or an already-built `LayoutEl[]` (materializeDef is
|
|
5192
|
-
* a no-op on an already-built tree — see fields.ts's own note on that) — either way, the
|
|
5193
|
-
* RETURNED object's `fields` is always the built `LayoutEl[]`: the input type widens, the
|
|
5194
|
-
* materialized type never does.
|
|
5195
|
-
*/
|
|
5196
4910
|
function materializeDef(def) {
|
|
5197
4911
|
const fields = Array.isArray(def.fields) ? def.fields : materializeTree(def.fields);
|
|
5198
4912
|
return {
|
|
@@ -5201,25 +4915,7 @@ function materializeDef(def) {
|
|
|
5201
4915
|
};
|
|
5202
4916
|
}
|
|
5203
4917
|
//#endregion
|
|
5204
|
-
//#region ../sdk/
|
|
5205
|
-
/**
|
|
5206
|
-
* Composite value parts.
|
|
5207
|
-
*
|
|
5208
|
-
* A composite field type declares its ROLES (what the pieces of its value mean); the
|
|
5209
|
-
* author of a shelf supplies FILLINGS for those roles through `parts`. The storage
|
|
5210
|
-
* columns of the composite are derived from the result instead of being written by
|
|
5211
|
-
* hand, and each piece is an ordinary field — typed, labelled and renderable.
|
|
5212
|
-
*
|
|
5213
|
-
* The role set is closed: consumers (unit conversion, formatting, describe_shelf) rely
|
|
5214
|
-
* on knowing which piece is the magnitude and which is the unit. What FILLS a role is
|
|
5215
|
-
* open: a magnitude may be real/int/rating, a unit may be a select or a relation.
|
|
5216
|
-
*/
|
|
5217
|
-
/**
|
|
5218
|
-
* `NormalizedOpts` (what `FieldDecl.opts` actually is) has no single `ui` property — the
|
|
5219
|
-
* author's `ui` is flattened into individual fields (`hidden`, `display`, `kind`, …)
|
|
5220
|
-
* by `normalizeOpts`. A correction that wants to carry presentation forward has to collect
|
|
5221
|
-
* those flat fields back into the nested shape `declare()`'s own `normalizeOpts` call expects.
|
|
5222
|
-
*/
|
|
4918
|
+
//#region ../sdk/dist/parts.js
|
|
5223
4919
|
var UI_KEYS = [
|
|
5224
4920
|
"hidden",
|
|
5225
4921
|
"noEditControl",
|
|
@@ -5232,58 +4928,29 @@ var UI_KEYS = [
|
|
|
5232
4928
|
"compareWith",
|
|
5233
4929
|
"span"
|
|
5234
4930
|
];
|
|
5235
|
-
/** The author's presentation opts, re-nested from `opts`'s flat fields — `undefined` when
|
|
5236
|
-
* none were set, so a correction never adds an empty `ui: {}` no filling ever had. */
|
|
5237
4931
|
function uiOf(opts) {
|
|
5238
4932
|
if (!opts) return void 0;
|
|
5239
4933
|
const v = {};
|
|
5240
4934
|
for (const k of UI_KEYS) if (opts[k] !== void 0) v[k] = opts[k];
|
|
5241
4935
|
return Object.keys(v).length ? v : void 0;
|
|
5242
4936
|
}
|
|
5243
|
-
/** A filling built from a ported factory arrives as a FieldDecl — build it into the
|
|
5244
|
-
* node or bare FieldMeta it represents; anything else (an unported factory's
|
|
5245
|
-
* result, or an already-built filling) passes through unchanged. `name` (the role) is
|
|
5246
|
-
* the part's own storage key (`ResolvedPart.key` is always the role, see below) — passing
|
|
5247
|
-
* it to `materialize` is what lets `wrapKey` recognize a computed filling as computed
|
|
5248
|
-
* (its `computed` test requires a key) and stamp `compute`/`derived`/`hints.noEditControl`
|
|
5249
|
-
* on the built meta, exactly as it would for any other keyed field. */
|
|
5250
4937
|
function built(input, name) {
|
|
5251
4938
|
return isDecl(input) ? materialize(input, name) : input;
|
|
5252
4939
|
}
|
|
5253
|
-
/**
|
|
5254
|
-
* Whether `def` admits `d` as-is — the diagnostic question `RoleDef.accepts` answers.
|
|
5255
|
-
* `resolveParts` uses this to decide whether a correction happened (and is worth logging);
|
|
5256
|
-
* a role's own `normalize` uses it to decide whether to return the filling unchanged.
|
|
5257
|
-
*/
|
|
5258
4940
|
function accepts(def, d) {
|
|
5259
4941
|
return Array.isArray(def.accepts) ? def.accepts.includes(typeOf(d.factory).prim) : def.accepts.test(d);
|
|
5260
4942
|
}
|
|
5261
|
-
/**
|
|
5262
|
-
* The correction every role gets for free: keep what the author said ABOUT the field
|
|
5263
|
-
* (`label`, `required`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
|
|
5264
|
-
* drop what belonged to the OLD type (`rules` — a number's min/max mean nothing to a
|
|
5265
|
-
* picture — and `multiple`, because a composite role owns exactly one column). A role whose
|
|
5266
|
-
* own default needs more than a bare factory (`measured`'s unit, `period`'s endpoints)
|
|
5267
|
-
* writes its own `normalize` instead of using this.
|
|
5268
|
-
*/
|
|
5269
4943
|
function retypeTo(factory) {
|
|
5270
4944
|
return (d) => {
|
|
5271
|
-
const { label,
|
|
4945
|
+
const { label, value } = d?.opts ?? {};
|
|
5272
4946
|
return declare(factory, {
|
|
5273
4947
|
label,
|
|
5274
|
-
required,
|
|
5275
4948
|
value,
|
|
5276
4949
|
noSearch: d?.opts.noSearch,
|
|
5277
4950
|
ui: uiOf(d?.opts)
|
|
5278
4951
|
});
|
|
5279
4952
|
};
|
|
5280
4953
|
}
|
|
5281
|
-
/**
|
|
5282
|
-
* Build a RoleDef whose correction is the shared default: accept a filling `accepts`
|
|
5283
|
-
* admits UNCHANGED, otherwise `retypeTo(defaultFactory)`. The common case — every role that
|
|
5284
|
-
* needs no per-instance default (a currency scaled to the field's own `units`, an endpoint
|
|
5285
|
-
* at the composite's own granularity) reads as one line.
|
|
5286
|
-
*/
|
|
5287
4954
|
function role(name, acceptsSpec, defaultFactory) {
|
|
5288
4955
|
const retype = retypeTo(defaultFactory);
|
|
5289
4956
|
const def = {
|
|
@@ -5293,28 +4960,15 @@ function role(name, acceptsSpec, defaultFactory) {
|
|
|
5293
4960
|
};
|
|
5294
4961
|
return def;
|
|
5295
4962
|
}
|
|
5296
|
-
/**
|
|
5297
|
-
* THE NAMING RULE for a composite: every part lives under its ROLE name, always — in the
|
|
5298
|
-
* value object (`{ value: 5, unit: 'kg' }`) and in storage (column `<field key>__<role>`,
|
|
5299
|
-
* `partColumns`). Consumers that reason about MEANING (a widget looking for the unit, unit
|
|
5300
|
-
* conversion, formatting) may hard-code the role name as a property name directly; this
|
|
5301
|
-
* helper exists only so a caller that already has a `ResolvedPart`/`PartSlot` need not know
|
|
5302
|
-
* that.
|
|
5303
|
-
*/
|
|
5304
4963
|
function partValueKey(p) {
|
|
5305
4964
|
return p.role;
|
|
5306
4965
|
}
|
|
5307
|
-
/** role → the key that role occupies in the value object. See `partValueKey`. */
|
|
5308
4966
|
function partValueKeys(parts) {
|
|
5309
4967
|
const out = {};
|
|
5310
4968
|
for (const p of parts) out[p.role] = p.role;
|
|
5311
4969
|
return out;
|
|
5312
4970
|
}
|
|
5313
4971
|
var NUMERIC_LIKE = /^\d+$/;
|
|
5314
|
-
/** The three shapes a factory returns, reduced to (key, meta, value). `name` is the
|
|
5315
|
-
* role this filling occupies — threaded to `built()` so a computed filling is built AS a
|
|
5316
|
-
* keyed field (see `built`'s own note); `ResolvedPart.key` below still always uses the
|
|
5317
|
-
* role directly, never this function's `key`. */
|
|
5318
4972
|
function readInput(input, name) {
|
|
5319
4973
|
input = built(input, name);
|
|
5320
4974
|
if (hasValue(input) && input.key !== void 0) return {
|
|
@@ -5333,18 +4987,6 @@ function readInput(input, name) {
|
|
|
5333
4987
|
value: void 0
|
|
5334
4988
|
};
|
|
5335
4989
|
}
|
|
5336
|
-
/**
|
|
5337
|
-
* Roles in declaration order, each with its filling. `owner` names the composite this
|
|
5338
|
-
* call resolves parts for (its own field key, supplied by the pipeline) — used only to
|
|
5339
|
-
* tag the one diagnostic line below.
|
|
5340
|
-
*
|
|
5341
|
-
* Throws with a message naming the composite's roles for an UNKNOWN role name — a typo
|
|
5342
|
-
* there fails loudly at `defineShelf` time rather than yielding a column that silently
|
|
5343
|
-
* never appears. It has nothing to say about a RENAMED storage key any more: a filling
|
|
5344
|
-
* carries no name of its own, so the role is the only source of one. A filling whose TYPE
|
|
5345
|
-
* the role does not accept is never rejected either: the role is a GUARD, not a filter, and
|
|
5346
|
-
* CORRECTS it instead (`RoleDef.normalize`) — see this module's header note.
|
|
5347
|
-
*/
|
|
5348
4990
|
function resolveParts(roles, spec, owner) {
|
|
5349
4991
|
const known = new Set(roles.map((r) => r.role));
|
|
5350
4992
|
for (const r of roles) if (r.role.includes("__")) throw new Error(`parts: role name '${r.role}' contains '__', the storage path separator`);
|
|
@@ -5393,25 +5035,6 @@ function resolveParts(roles, spec, owner) {
|
|
|
5393
5035
|
for (const p of out) if (p.meta.parts) assertInertNesting(p.meta, `${owner ?? "(unkeyed)"}.${p.key}`);
|
|
5394
5036
|
return out;
|
|
5395
5037
|
}
|
|
5396
|
-
/**
|
|
5397
|
-
* A composite filling a composite's role STORES fine at any depth: `partColumns` expands
|
|
5398
|
-
* it, and the value↔columns walks (`flattenComposite`/`nestComposite`) follow it. The
|
|
5399
|
-
* COMPUTE side does not follow it yet. Every engine that addresses a composite part reaches
|
|
5400
|
-
* exactly ONE level below a field:
|
|
5401
|
-
*
|
|
5402
|
-
* - `completeValue` / `resolveField` (server/compute-unit.ts) — runs a part's `value`
|
|
5403
|
-
* - `applyStoredParts` (server/part-injection.ts) — writes the computed column
|
|
5404
|
-
* - `cacheClockColumns` / `levelClockColumnsByOwner` (server/cache-clock.ts) — the
|
|
5405
|
-
* `__cachedAt` column a `cache` needs to ever refresh
|
|
5406
|
-
* - the file normalizer (server/file-fields.ts) — resolves a part's `{name}` pointer
|
|
5407
|
-
* - `collectMutateSources` / `collectComputeUnits` (sdk/mutate-graph.ts) — the trigger graph
|
|
5408
|
-
*
|
|
5409
|
-
* A part BELOW that level carrying any of those would own a real column that nothing ever
|
|
5410
|
-
* writes, refreshes, validates or triggers — the exact silent failure a storage change must
|
|
5411
|
-
* not ship. So it is refused where it is DECLARED, naming the path, instead of failing
|
|
5412
|
-
* quietly at runtime. Lifting this is a compute-engine change, not a storage one: nothing
|
|
5413
|
-
* about the columns needs to move for it.
|
|
5414
|
-
*/
|
|
5415
5038
|
function assertInertNesting(meta, path) {
|
|
5416
5039
|
for (const p of meta.parts ?? []) {
|
|
5417
5040
|
const where = `${path}.${p.key}`;
|
|
@@ -5425,29 +5048,6 @@ function assertInertNesting(meta, path) {
|
|
|
5425
5048
|
if (p.meta.parts) assertInertNesting(p.meta, where);
|
|
5426
5049
|
}
|
|
5427
5050
|
}
|
|
5428
|
-
/**
|
|
5429
|
-
* Storage columns of a composite: sub-PATH → column type. Every part contributes its own
|
|
5430
|
-
* columns, whatever they are.
|
|
5431
|
-
*
|
|
5432
|
-
* A part is a NODE, not necessarily a leaf: a filling that is itself a composite carries
|
|
5433
|
-
* its own `columns`, and those are what it contributes — under its role name as a prefix,
|
|
5434
|
-
* joined by the same `__` the physical column name uses (`at` filled by `f.geo` →
|
|
5435
|
-
* `at__lat`/`at__lng`/`at__label`). A part with no `columns` is a scalar and contributes
|
|
5436
|
-
* exactly one entry, its own `column`, under its role name. That is where the walk STOPS,
|
|
5437
|
-
* and every walk stops there: a scalar is the only thing a composite can bottom out in.
|
|
5438
|
-
*
|
|
5439
|
-
* The loop is one level deep because the result is built INDUCTIVELY, not by re-walking a
|
|
5440
|
-
* tree. A filling is materialized before the composite that holds it reads it, so
|
|
5441
|
-
* `p.meta.columns` was already produced by this same function and already carries the FULL
|
|
5442
|
-
* sub-path of everything below it. Depth therefore costs nothing here and is bounded by
|
|
5443
|
-
* nothing but the column NAME the joined path produces — see `storageColumns` (shelf.ts)
|
|
5444
|
-
* for the join, and `MAX_COLUMN_NAME` (compose.ts) for the budget that name must fit.
|
|
5445
|
-
*
|
|
5446
|
-
* The result is the FLAT storage projection of the composite. The STRUCTURE lives in
|
|
5447
|
-
* `FieldMeta.parts`, which is what the value↔columns walks (`nestComposite`/
|
|
5448
|
-
* `flattenComposite` in shelf.ts) read: they need to know where one nested object ends and
|
|
5449
|
-
* the next begins, and a joined key cannot say that on its own.
|
|
5450
|
-
*/
|
|
5451
5051
|
function partColumns(parts) {
|
|
5452
5052
|
const cols = {};
|
|
5453
5053
|
for (const p of parts) {
|
|
@@ -5457,81 +5057,15 @@ function partColumns(parts) {
|
|
|
5457
5057
|
}
|
|
5458
5058
|
return cols;
|
|
5459
5059
|
}
|
|
5460
|
-
function partsRowShape(shape, parts
|
|
5060
|
+
function partsRowShape(shape, parts) {
|
|
5461
5061
|
const out = { ...shape };
|
|
5462
5062
|
for (const p of parts) if (p.mode === "computedStored") out[p.key] = unknown().optional();
|
|
5463
5063
|
else {
|
|
5464
5064
|
const own = p.overridden ? p.meta.zod : shape[p.role] ?? unknown().optional();
|
|
5465
|
-
|
|
5466
|
-
out[p.key] = relaxed ? tolerance === "null-or-missing" ? own.nullish() : own.nullable() : own;
|
|
5065
|
+
out[p.key] = own.nullable();
|
|
5467
5066
|
}
|
|
5468
5067
|
return out;
|
|
5469
5068
|
}
|
|
5470
|
-
/**
|
|
5471
|
-
* MIXED OWNERSHIP of the STORED parts: the composite has a computed-and-stored part (the
|
|
5472
|
-
* server owns that column) AND a plain stored part (the client owns that one). Returns the
|
|
5473
|
-
* two role lists, or null when the composite is uniformly owned.
|
|
5474
|
-
*
|
|
5475
|
-
* Such a composite IS materialized by the write path (`applyStoredParts`): the server's part
|
|
5476
|
-
* is computed on every write, whether or not the write carries the field, and the client's
|
|
5477
|
-
* columns keep whatever the record already holds. A computed sum exists whether or not a
|
|
5478
|
-
* user has picked a unit, and a NULL column is invisible to SQL. See THE ABSENT-COMPOSITE
|
|
5479
|
-
* RULE in `@coffer-org/server/part-injection` and `docs/decisions.md` (2026-08-12).
|
|
5480
|
-
*
|
|
5481
|
-
* The one shape this is still wrong for is a composite that MANDATES one of those client-owned
|
|
5482
|
-
* roles — see `mandatedClientParts` below.
|
|
5483
|
-
*/
|
|
5484
|
-
function mixedOwnedStoredParts(parts) {
|
|
5485
|
-
const server = parts.filter((p) => p.mode === "computedStored").map((p) => p.role);
|
|
5486
|
-
const client = parts.filter((p) => p.mode === "stored").map((p) => p.role);
|
|
5487
|
-
return server.length > 0 && client.length > 0 ? {
|
|
5488
|
-
server,
|
|
5489
|
-
client
|
|
5490
|
-
} : null;
|
|
5491
|
-
}
|
|
5492
|
-
/**
|
|
5493
|
-
* The CLIENT-OWNED stored roles this composite demands a value for — the roles a
|
|
5494
|
-
* server-materialized half-value would leave empty in breach of the field's own contract.
|
|
5495
|
-
* Empty list → nothing is mandated and the composite is safe to materialize.
|
|
5496
|
-
*
|
|
5497
|
-
* THE MANDATE HAS TWO SOURCES, and reading only the first is a bug this had:
|
|
5498
|
-
*
|
|
5499
|
-
* - the FIELD's own `required`, which `partsRowShape` turns into `tolerateNull = false` for
|
|
5500
|
-
* every client-owned slot;
|
|
5501
|
-
* - the FILLING's `required`, per part: `partsRowShape` keeps an OVERRIDDEN part's own
|
|
5502
|
-
* mandate ("a filling the author declared `required` keeps its mandate"), so
|
|
5503
|
-
* `fields: { unit: f.string({ required: true, … }) }` makes THAT slot non-nullable even
|
|
5504
|
-
* when the field around it is optional.
|
|
5505
|
-
*
|
|
5506
|
-
* Either way the materialized value — the server's column filled, the client's empty — reads
|
|
5507
|
-
* back as `{value: 5, unit: null}` and is rejected by the composite's own zod with
|
|
5508
|
-
* `{"code":"required"}` the moment a record form or an MCP agent feeds it straight back. So
|
|
5509
|
-
* both sources must count, and the write path refuses to materialize when either speaks.
|
|
5510
|
-
*
|
|
5511
|
-
* A CONDITION counts too, though its harm is different and milder. `partsRowShape` compares
|
|
5512
|
-
* `=== true`, so a conditional mandate does not make the slot non-nullable and the round trip
|
|
5513
|
-
* still parses; what it does instead is let a materialized object satisfy
|
|
5514
|
-
* `updateRecord`'s conditional-required check (which only asks whether the value is non-null),
|
|
5515
|
-
* excusing the user from a field the schema says is mandatory. Nothing static can evaluate a
|
|
5516
|
-
* Condition here, so any mandate that is not literally `false`/absent is treated as speaking —
|
|
5517
|
-
* conservative in the safe direction, since refusing to materialize is exactly the older
|
|
5518
|
-
* behaviour and loses nothing.
|
|
5519
|
-
*/
|
|
5520
|
-
function mandatedClientParts(required, parts) {
|
|
5521
|
-
const speaks = (flag) => flag !== void 0 && flag !== false;
|
|
5522
|
-
const field = speaks(required);
|
|
5523
|
-
return parts.filter((p) => p.mode === "stored" && (field || p.overridden && speaks(p.meta.required))).map((p) => p.role);
|
|
5524
|
-
}
|
|
5525
|
-
/**
|
|
5526
|
-
* Drops SERVER-OWNED part slots from a successfully parsed composite row: a computed-and-stored
|
|
5527
|
-
* role, keyed by its role name. Its value belongs to the schema, not to the writer — it is
|
|
5528
|
-
* recomputed and written by the server on the same write (`applyStoredParts`) — so a client
|
|
5529
|
-
* sending one back is stripped rather than rejected: the schema wins either way.
|
|
5530
|
-
*
|
|
5531
|
-
* Stripping (not `z.never()`-rejecting, as a computed-and-stored top-level FIELD does) is
|
|
5532
|
-
* what keeps a read → PATCH-the-whole-object round trip working: a read hands the client that
|
|
5533
|
-
* slot straight out of its column, so rejecting the echo would reject every full-object PATCH.
|
|
5534
|
-
*/
|
|
5535
5069
|
function stripServerOwnedParts(value, parts) {
|
|
5536
5070
|
let out = value;
|
|
5537
5071
|
for (const p of parts) if (p.mode === "computedStored" && p.key in out) {
|
|
@@ -5541,41 +5075,17 @@ function stripServerOwnedParts(value, parts) {
|
|
|
5541
5075
|
return out;
|
|
5542
5076
|
}
|
|
5543
5077
|
//#endregion
|
|
5544
|
-
//#region ../sdk/
|
|
5545
|
-
/**
|
|
5546
|
-
* Field presets are thin wrappers around the primitives in fields.ts.
|
|
5547
|
-
*
|
|
5548
|
-
* Each preset = one kind (one widget). Presets do not accept `format`;
|
|
5549
|
-
* they are semantic types themselves. min/max/step go through `config`.
|
|
5550
|
-
*
|
|
5551
|
-
* Presets are added to `f` through `composeF`, which guarantees no preset
|
|
5552
|
-
* overrides a primitive.
|
|
5553
|
-
*
|
|
5554
|
-
* The cyclic import from fields.ts is safe: factories/helpers are hoisted declarations,
|
|
5555
|
-
* and presets call them only inside their function bodies.
|
|
5556
|
-
*/
|
|
5557
|
-
/**
|
|
5558
|
-
* Email — kind 'email', email validation, inputType 'email'.
|
|
5559
|
-
*
|
|
5560
|
-
* @layer preset
|
|
5561
|
-
* @base string
|
|
5562
|
-
* @prim text
|
|
5563
|
-
* @widget email
|
|
5564
|
-
* @example f.email({ label: 'mod.fields.contact' })
|
|
5565
|
-
*/
|
|
5078
|
+
//#region ../sdk/dist/field-presets.js
|
|
5566
5079
|
function email(o) {
|
|
5567
5080
|
return declare("email", o);
|
|
5568
5081
|
}
|
|
5569
5082
|
registerPreset("email", "string", {}, {
|
|
5570
5083
|
kind: "email",
|
|
5571
5084
|
widget: "email",
|
|
5572
|
-
build(
|
|
5573
|
-
const required = o.required ?? false;
|
|
5574
|
-
let s = string$1().email({ message: vmsg("email") });
|
|
5575
|
-
if (required) s = s.min(1, { message: vmsg("min_length", { min: 1 }) });
|
|
5085
|
+
build() {
|
|
5576
5086
|
return {
|
|
5577
5087
|
column: "text",
|
|
5578
|
-
zod:
|
|
5088
|
+
zod: string$1().email({ message: vmsg("email") }),
|
|
5579
5089
|
hints: {
|
|
5580
5090
|
format: "email",
|
|
5581
5091
|
inputType: "email"
|
|
@@ -5583,27 +5093,16 @@ registerPreset("email", "string", {}, {
|
|
|
5583
5093
|
};
|
|
5584
5094
|
}
|
|
5585
5095
|
});
|
|
5586
|
-
/**
|
|
5587
|
-
* Phone — kind 'tel', loose format /^\+?[\d\s()-]{4,}$/, inputType 'tel'.
|
|
5588
|
-
*
|
|
5589
|
-
* @layer preset
|
|
5590
|
-
* @base string
|
|
5591
|
-
* @prim text
|
|
5592
|
-
* @widget tel
|
|
5593
|
-
* @example f.tel({ label: 'mod.fields.phone' })
|
|
5594
|
-
*/
|
|
5595
5096
|
function tel(o) {
|
|
5596
5097
|
return declare("tel", o);
|
|
5597
5098
|
}
|
|
5598
5099
|
registerPreset("tel", "string", {}, {
|
|
5599
5100
|
kind: "tel",
|
|
5600
5101
|
widget: "tel",
|
|
5601
|
-
build(
|
|
5602
|
-
const required = o.required ?? false;
|
|
5603
|
-
const base_z = string$1().regex(TEL_RE, { message: vmsg("pattern", { messageKey: "core.presets.tel" }) });
|
|
5102
|
+
build() {
|
|
5604
5103
|
return {
|
|
5605
5104
|
column: "text",
|
|
5606
|
-
zod:
|
|
5105
|
+
zod: string$1().regex(TEL_RE, { message: vmsg("pattern", { messageKey: "core.presets.tel" }) }),
|
|
5607
5106
|
hints: {
|
|
5608
5107
|
format: "tel",
|
|
5609
5108
|
inputType: "tel"
|
|
@@ -5611,15 +5110,6 @@ registerPreset("tel", "string", {}, {
|
|
|
5611
5110
|
};
|
|
5612
5111
|
}
|
|
5613
5112
|
});
|
|
5614
|
-
/**
|
|
5615
|
-
* Password — kind 'password', inputType 'password'. No strict schema. No multiple.
|
|
5616
|
-
*
|
|
5617
|
-
* @layer preset
|
|
5618
|
-
* @base string
|
|
5619
|
-
* @prim text
|
|
5620
|
-
* @widget password
|
|
5621
|
-
* @example f.password({ label: 'mod.fields.password' })
|
|
5622
|
-
*/
|
|
5623
5113
|
function password(o) {
|
|
5624
5114
|
return declare("password", o);
|
|
5625
5115
|
}
|
|
@@ -5627,10 +5117,10 @@ registerPreset("password", "string", {}, {
|
|
|
5627
5117
|
kind: "password",
|
|
5628
5118
|
widget: "password",
|
|
5629
5119
|
selfManages: /* @__PURE__ */ new Set(["multiple"]),
|
|
5630
|
-
build(
|
|
5120
|
+
build() {
|
|
5631
5121
|
return {
|
|
5632
5122
|
column: "text",
|
|
5633
|
-
zod:
|
|
5123
|
+
zod: string$1(),
|
|
5634
5124
|
hints: {
|
|
5635
5125
|
format: "password",
|
|
5636
5126
|
inputType: "password"
|
|
@@ -5638,20 +5128,6 @@ registerPreset("password", "string", {}, {
|
|
|
5638
5128
|
};
|
|
5639
5129
|
}
|
|
5640
5130
|
});
|
|
5641
|
-
/**
|
|
5642
|
-
* Internal API-token list — keyValue-shaped collection (name + write-only
|
|
5643
|
-
* token + timestamps), custom renderer (`kind:'internalApiToken'`, own
|
|
5644
|
-
* create/revoke UI — same trick as url()/perWeekday()). "Internal" = not a
|
|
5645
|
-
* general-purpose field type for plugin shelves, only for the core account
|
|
5646
|
-
* settings page. `token` is `kind:'password'` so maskSecrets/preserveTree
|
|
5647
|
-
* already mask/preserve it for free.
|
|
5648
|
-
*
|
|
5649
|
-
* @layer preset
|
|
5650
|
-
* @base group
|
|
5651
|
-
* @prim —
|
|
5652
|
-
* @widget internalApiToken
|
|
5653
|
-
* @example f.internalApiToken({ label: 'mod.fields.apiTokens' })
|
|
5654
|
-
*/
|
|
5655
5131
|
function internalApiToken(o) {
|
|
5656
5132
|
return group({
|
|
5657
5133
|
scope: "nest",
|
|
@@ -5667,18 +5143,6 @@ function internalApiToken(o) {
|
|
|
5667
5143
|
}
|
|
5668
5144
|
});
|
|
5669
5145
|
}
|
|
5670
|
-
/**
|
|
5671
|
-
* Internal "connected apps" list — the OAuth clients (claude.ai & co) the user
|
|
5672
|
-
* has authorized for MCP access. Same "internal" caveat and custom-renderer
|
|
5673
|
-
* trick as internalApiToken(): read-only rows + a revoke action, never part of
|
|
5674
|
-
* the parent form's save.
|
|
5675
|
-
*
|
|
5676
|
-
* @layer preset
|
|
5677
|
-
* @base group
|
|
5678
|
-
* @prim —
|
|
5679
|
-
* @widget internalOauthGrants
|
|
5680
|
-
* @example f.internalOauthGrants({ label: 'mod.fields.oauthGrants' })
|
|
5681
|
-
*/
|
|
5682
5146
|
function internalOauthGrants(o) {
|
|
5683
5147
|
return group({
|
|
5684
5148
|
scope: "nest",
|
|
@@ -5692,16 +5156,20 @@ function internalOauthGrants(o) {
|
|
|
5692
5156
|
}
|
|
5693
5157
|
});
|
|
5694
5158
|
}
|
|
5159
|
+
function internalIdentityLinks(o) {
|
|
5160
|
+
return group({
|
|
5161
|
+
scope: "nest",
|
|
5162
|
+
label: o.label,
|
|
5163
|
+
multiple: true,
|
|
5164
|
+
ui: { kind: "internalIdentityLinks" },
|
|
5165
|
+
fields: {
|
|
5166
|
+
provider: string({}),
|
|
5167
|
+
externalId: string({}),
|
|
5168
|
+
linkedAt: string({})
|
|
5169
|
+
}
|
|
5170
|
+
});
|
|
5171
|
+
}
|
|
5695
5172
|
var SLUG_RE = /^[a-z0-9-]+$/;
|
|
5696
|
-
/**
|
|
5697
|
-
* Slug — kind 'slug', `^[a-z0-9-]+$`.
|
|
5698
|
-
*
|
|
5699
|
-
* @layer preset
|
|
5700
|
-
* @base string
|
|
5701
|
-
* @prim text
|
|
5702
|
-
* @widget slug
|
|
5703
|
-
* @example f.slug({ label: 'mod.fields.slug' })
|
|
5704
|
-
*/
|
|
5705
5173
|
function slug(o) {
|
|
5706
5174
|
return declare("slug", o);
|
|
5707
5175
|
}
|
|
@@ -5716,21 +5184,6 @@ registerPreset("slug", "string", {}, {
|
|
|
5716
5184
|
};
|
|
5717
5185
|
}
|
|
5718
5186
|
});
|
|
5719
|
-
/**
|
|
5720
|
-
* Identifier — a code printed for a machine and read back by one: a serial number, an order
|
|
5721
|
-
* number, a policy number, an IMEI. What a person actually DOES with one is copy it, which is
|
|
5722
|
-
* why this is a type of its own and not `f.string({ ui: { voice: 'data' } })`: the machine face
|
|
5723
|
-
* is one word on any string now, but the click that copies belongs to the kind.
|
|
5724
|
-
*
|
|
5725
|
-
* Deliberately unvalidated beyond a length: an identifier's format belongs to whoever issued
|
|
5726
|
-
* it, and a pattern here would reject the next manufacturer's.
|
|
5727
|
-
*
|
|
5728
|
-
* @layer preset
|
|
5729
|
-
* @base string
|
|
5730
|
-
* @prim text
|
|
5731
|
-
* @widget identifier
|
|
5732
|
-
* @example f.identifier({ label: 'mod.fields.serialNumber' })
|
|
5733
|
-
*/
|
|
5734
5187
|
function identifier(o) {
|
|
5735
5188
|
return declare("identifier", o);
|
|
5736
5189
|
}
|
|
@@ -5745,20 +5198,6 @@ registerPreset("identifier", "string", {}, {
|
|
|
5745
5198
|
};
|
|
5746
5199
|
}
|
|
5747
5200
|
});
|
|
5748
|
-
/**
|
|
5749
|
-
* The machine-readable zone of a travel document — the identifier taken to its limit: printed
|
|
5750
|
-
* for a scanner, not for a person. Fixed pitch, chevrons kept as the filler they are, and the
|
|
5751
|
-
* line breaks preserved, because an MRZ's line structure is part of what it encodes.
|
|
5752
|
-
*
|
|
5753
|
-
* Unvalidated on purpose, like `identifier`: the ICAO line formats differ by document type, and
|
|
5754
|
-
* a strip transcribed from a real document is worth storing even when it does not check out.
|
|
5755
|
-
*
|
|
5756
|
-
* @layer preset
|
|
5757
|
-
* @base string
|
|
5758
|
-
* @prim text
|
|
5759
|
-
* @widget mrz
|
|
5760
|
-
* @example f.mrz({ label: 'documents.personal_document.fields.mrz' })
|
|
5761
|
-
*/
|
|
5762
5201
|
function mrz(o) {
|
|
5763
5202
|
return declare("mrz", o);
|
|
5764
5203
|
}
|
|
@@ -5774,15 +5213,6 @@ registerPreset("mrz", "string", {}, {
|
|
|
5774
5213
|
}
|
|
5775
5214
|
});
|
|
5776
5215
|
var COLOR_RE = /^#[0-9a-fA-F]{6}$/;
|
|
5777
|
-
/**
|
|
5778
|
-
* Hex color — kind 'color', `#rrggbb` + swatch widget.
|
|
5779
|
-
*
|
|
5780
|
-
* @layer preset
|
|
5781
|
-
* @base string
|
|
5782
|
-
* @prim text
|
|
5783
|
-
* @widget color
|
|
5784
|
-
* @example f.color({ label: 'mod.fields.color' })
|
|
5785
|
-
*/
|
|
5786
5216
|
function color(o) {
|
|
5787
5217
|
return declare("color", o);
|
|
5788
5218
|
}
|
|
@@ -5797,74 +5227,44 @@ registerPreset("color", "string", {}, {
|
|
|
5797
5227
|
};
|
|
5798
5228
|
}
|
|
5799
5229
|
});
|
|
5800
|
-
/**
|
|
5801
|
-
* CSS color name — kind 'colorname'.
|
|
5802
|
-
*
|
|
5803
|
-
* @layer preset
|
|
5804
|
-
* @base string
|
|
5805
|
-
* @prim text
|
|
5806
|
-
* @widget colorname
|
|
5807
|
-
* @example f.colorname({ label: 'mod.fields.colorName' })
|
|
5808
|
-
*/
|
|
5809
5230
|
function colorname(o) {
|
|
5810
5231
|
return declare("colorname", o);
|
|
5811
5232
|
}
|
|
5812
5233
|
registerPreset("colorname", "string", {}, {
|
|
5813
5234
|
kind: "colorname",
|
|
5814
5235
|
widget: "colorname",
|
|
5815
|
-
build(
|
|
5236
|
+
build() {
|
|
5816
5237
|
return {
|
|
5817
5238
|
column: "text",
|
|
5818
|
-
zod:
|
|
5239
|
+
zod: string$1().refine((v) => CSS_COLOR_NAMES.has(v.toLowerCase()), { message: vmsg("colorname") }),
|
|
5819
5240
|
hints: {}
|
|
5820
5241
|
};
|
|
5821
5242
|
}
|
|
5822
5243
|
});
|
|
5823
|
-
/**
|
|
5824
|
-
* Shelf heading — kind 'title'. Heading renderer.
|
|
5825
|
-
*
|
|
5826
|
-
* @layer preset
|
|
5827
|
-
* @base string
|
|
5828
|
-
* @prim text
|
|
5829
|
-
* @widget title
|
|
5830
|
-
* @example f.title({ label: 'mod.fields.name' })
|
|
5831
|
-
*/
|
|
5832
5244
|
function title(o) {
|
|
5833
5245
|
return declare("title", o);
|
|
5834
5246
|
}
|
|
5835
5247
|
registerPreset("title", "string", {}, {
|
|
5836
5248
|
kind: "title",
|
|
5837
5249
|
widget: "title",
|
|
5838
|
-
build(
|
|
5250
|
+
build() {
|
|
5839
5251
|
return {
|
|
5840
5252
|
column: "text",
|
|
5841
|
-
zod:
|
|
5253
|
+
zod: string$1(),
|
|
5842
5254
|
hints: {}
|
|
5843
5255
|
};
|
|
5844
5256
|
}
|
|
5845
5257
|
});
|
|
5846
|
-
/**
|
|
5847
|
-
* Link — kind 'link', edited as string, view = clickable <a target=_blank>.
|
|
5848
|
-
* Stores the raw URL as text (unlike composite f.url). Loose validation.
|
|
5849
|
-
*
|
|
5850
|
-
* @layer preset
|
|
5851
|
-
* @base string
|
|
5852
|
-
* @prim text
|
|
5853
|
-
* @widget link
|
|
5854
|
-
* @example f.link({ label: 'mod.fields.link' })
|
|
5855
|
-
*/
|
|
5856
5258
|
function link(o) {
|
|
5857
5259
|
return declare("link", o);
|
|
5858
5260
|
}
|
|
5859
5261
|
registerPreset("link", "string", {}, {
|
|
5860
5262
|
kind: "link",
|
|
5861
5263
|
widget: "link",
|
|
5862
|
-
build(
|
|
5863
|
-
const required = o.required ?? false;
|
|
5864
|
-
const base_z = string$1().regex(LINK_RE, { message: vmsg("pattern", { messageKey: "core.presets.link" }) });
|
|
5264
|
+
build() {
|
|
5865
5265
|
return {
|
|
5866
5266
|
column: "text",
|
|
5867
|
-
zod:
|
|
5267
|
+
zod: string$1().regex(LINK_RE, { message: vmsg("pattern", { messageKey: "core.presets.link" }) }),
|
|
5868
5268
|
hints: {
|
|
5869
5269
|
format: "url",
|
|
5870
5270
|
inputType: "url"
|
|
@@ -5873,9 +5273,7 @@ registerPreset("link", "string", {}, {
|
|
|
5873
5273
|
}
|
|
5874
5274
|
});
|
|
5875
5275
|
var TEL_RE = /^\+?[\d\s()-]{4,}$/;
|
|
5876
|
-
/** Loose URL: any scheme:// OR dotted host (optional port/path). No spaces. */
|
|
5877
5276
|
var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
|
|
5878
|
-
/** CSS named colors (CSS Color Module L4) for f.colorname. */
|
|
5879
5277
|
var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
|
|
5880
5278
|
"aliceblue",
|
|
5881
5279
|
"antiquewhite",
|
|
@@ -6027,15 +5425,6 @@ var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
|
|
|
6027
5425
|
"yellowgreen",
|
|
6028
5426
|
"transparent"
|
|
6029
5427
|
]);
|
|
6030
|
-
/**
|
|
6031
|
-
* Tag — non-empty string + chip widget. `multiple` → array of tags (`tags`).
|
|
6032
|
-
*
|
|
6033
|
-
* @layer preset
|
|
6034
|
-
* @base string
|
|
6035
|
-
* @prim text
|
|
6036
|
-
* @widget tag
|
|
6037
|
-
* @example f.tag({ label: 'mod.fields.tag' })
|
|
6038
|
-
*/
|
|
6039
5428
|
function tag(o) {
|
|
6040
5429
|
return declare("tag", o);
|
|
6041
5430
|
}
|
|
@@ -6056,21 +5445,6 @@ registerPreset("tag", "string", {}, {
|
|
|
6056
5445
|
};
|
|
6057
5446
|
}
|
|
6058
5447
|
});
|
|
6059
|
-
/**
|
|
6060
|
-
* A location written the way it is said aloud: workshop › shelf 2 › box. A `string` fixed to
|
|
6061
|
-
* `multiple`, exactly as `f.tags` is, because a place inside a place inside a place IS a
|
|
6062
|
-
* sequence — the plurality is the type, not a modifier someone remembered to add.
|
|
6063
|
-
*
|
|
6064
|
-
* Distinct from `f.tags`, which stores the same shape: tags are an unordered SET, and a path is
|
|
6065
|
-
* an ordered CHAIN where each step is inside the one before it. Drawn as chips, that
|
|
6066
|
-
* containment — the only thing the value actually carries — is lost.
|
|
6067
|
-
*
|
|
6068
|
-
* @layer preset
|
|
6069
|
-
* @base string
|
|
6070
|
-
* @prim text
|
|
6071
|
-
* @widget path
|
|
6072
|
-
* @example f.path({ label: 'things.storage_location.fields.path' })
|
|
6073
|
-
*/
|
|
6074
5448
|
function path(o) {
|
|
6075
5449
|
return declare("path", {
|
|
6076
5450
|
...o,
|
|
@@ -6088,30 +5462,12 @@ registerPreset("path", "string", {}, {
|
|
|
6088
5462
|
};
|
|
6089
5463
|
}
|
|
6090
5464
|
});
|
|
6091
|
-
/**
|
|
6092
|
-
* Tags — `tag({ multiple: true })`.
|
|
6093
|
-
*
|
|
6094
|
-
* @layer preset
|
|
6095
|
-
* @base string
|
|
6096
|
-
* @prim text
|
|
6097
|
-
* @widget tag
|
|
6098
|
-
* @example f.tags({ label: 'mod.fields.tags' })
|
|
6099
|
-
*/
|
|
6100
5465
|
function tags(o) {
|
|
6101
5466
|
return tag({
|
|
6102
5467
|
...o,
|
|
6103
5468
|
multiple: true
|
|
6104
5469
|
});
|
|
6105
5470
|
}
|
|
6106
|
-
/**
|
|
6107
|
-
* Markdown — text() + Markdown editor.
|
|
6108
|
-
*
|
|
6109
|
-
* @layer preset
|
|
6110
|
-
* @base text
|
|
6111
|
-
* @prim text
|
|
6112
|
-
* @widget markdown
|
|
6113
|
-
* @example f.markdown({ label: 'mod.fields.body' })
|
|
6114
|
-
*/
|
|
6115
5471
|
function markdown(o) {
|
|
6116
5472
|
return declare("markdown", o);
|
|
6117
5473
|
}
|
|
@@ -6119,16 +5475,6 @@ registerPreset("markdown", "text", {}, {
|
|
|
6119
5475
|
kind: "markdown",
|
|
6120
5476
|
widget: "markdown"
|
|
6121
5477
|
});
|
|
6122
|
-
/**
|
|
6123
|
-
* Code snippet — text() + monospace code editor.
|
|
6124
|
-
* `rules.language` is a highlighting hint (json/yaml/bash/ini…), stored in hints.
|
|
6125
|
-
*
|
|
6126
|
-
* @layer preset
|
|
6127
|
-
* @base text
|
|
6128
|
-
* @prim text
|
|
6129
|
-
* @widget snippet
|
|
6130
|
-
* @example f.snippet({ label: 'mod.fields.config' })
|
|
6131
|
-
*/
|
|
6132
5478
|
function snippet(o) {
|
|
6133
5479
|
return declare("snippet", o);
|
|
6134
5480
|
}
|
|
@@ -6147,22 +5493,9 @@ registerPreset("snippet", "text", {}, {
|
|
|
6147
5493
|
};
|
|
6148
5494
|
}
|
|
6149
5495
|
});
|
|
6150
|
-
/** A NUL byte, or U+FFFD left behind by decoding non-UTF-8 bytes as text. */
|
|
6151
5496
|
function isBinaryText(v) {
|
|
6152
5497
|
return v.includes("\0") || v.includes("�");
|
|
6153
5498
|
}
|
|
6154
|
-
/**
|
|
6155
|
-
* Source — plain text in one column, shaped like a file in the UI.
|
|
6156
|
-
* Written through the API or by uploading a file; never hand-edited in the web form
|
|
6157
|
-
* (hints.noEditControl). The content is never rendered — only name + Upload/Download.
|
|
6158
|
-
* `rules.ext` is the displayed/downloaded extension, `rules.maxBytes` the size cap.
|
|
6159
|
-
*
|
|
6160
|
-
* @layer preset
|
|
6161
|
-
* @base text
|
|
6162
|
-
* @prim text
|
|
6163
|
-
* @widget source
|
|
6164
|
-
* @example f.source({ label: 'mod.fields.file' })
|
|
6165
|
-
*/
|
|
6166
5499
|
function source(o) {
|
|
6167
5500
|
return declare("source", o);
|
|
6168
5501
|
}
|
|
@@ -6197,15 +5530,6 @@ registerPreset("source", "text", {}, {
|
|
|
6197
5530
|
};
|
|
6198
5531
|
}
|
|
6199
5532
|
});
|
|
6200
|
-
/**
|
|
6201
|
-
* Rating 1..N — integer + star widget. rules.max is the upper bound (default 5).
|
|
6202
|
-
*
|
|
6203
|
-
* @layer preset
|
|
6204
|
-
* @base int
|
|
6205
|
-
* @prim number
|
|
6206
|
-
* @widget rating
|
|
6207
|
-
* @example f.rating({ label: 'mod.fields.rating' })
|
|
6208
|
-
*/
|
|
6209
5533
|
function rating(o) {
|
|
6210
5534
|
return declare("rating", o);
|
|
6211
5535
|
}
|
|
@@ -6221,16 +5545,6 @@ registerPreset("rating", "int", { config: { max: 5 } }, {
|
|
|
6221
5545
|
};
|
|
6222
5546
|
}
|
|
6223
5547
|
});
|
|
6224
|
-
/** Duration in seconds — integer + duration widget ("2d 3h 30m 15s").
|
|
6225
|
-
* rules.max is the maximum in seconds (default 604800 = 1 week).
|
|
6226
|
-
* rules.step is the step in seconds (default 60 = 1 minute).
|
|
6227
|
-
*
|
|
6228
|
-
* @layer preset
|
|
6229
|
-
* @base int
|
|
6230
|
-
* @prim number
|
|
6231
|
-
* @widget duration
|
|
6232
|
-
* @example f.duration({ label: 'mod.fields.runtime' })
|
|
6233
|
-
*/
|
|
6234
5548
|
function duration(o) {
|
|
6235
5549
|
return declare("duration", o);
|
|
6236
5550
|
}
|
|
@@ -6254,15 +5568,6 @@ registerPreset("duration", "int", { config: {
|
|
|
6254
5568
|
};
|
|
6255
5569
|
}
|
|
6256
5570
|
});
|
|
6257
|
-
/** Reminder — date with visual urgency. kind 'reminder', prim 'date'.
|
|
6258
|
-
* rules.lead = day threshold (default 30). rules.min/rules.max = ISO dates.
|
|
6259
|
-
*
|
|
6260
|
-
* @layer preset
|
|
6261
|
-
* @base date
|
|
6262
|
-
* @prim date
|
|
6263
|
-
* @widget reminder
|
|
6264
|
-
* @example f.reminder({ label: 'mod.fields.renewal' })
|
|
6265
|
-
*/
|
|
6266
5571
|
function reminder(o) {
|
|
6267
5572
|
return declare("reminder", o);
|
|
6268
5573
|
}
|
|
@@ -6281,24 +5586,6 @@ registerPreset("reminder", "date", { lead: 30 }, {
|
|
|
6281
5586
|
};
|
|
6282
5587
|
}
|
|
6283
5588
|
});
|
|
6284
|
-
/** Timetable — a set of times of day, read as a schedule rather than as a bag of values.
|
|
6285
|
-
* kind 'timetable', prim 'time', always `multiple`.
|
|
6286
|
-
*
|
|
6287
|
-
* Same shape as `f.reminder`/`f.age` over `date`: a semantic type whose renderer knows what
|
|
6288
|
-
* the values MEAN. Everything it shows beyond the times themselves — when the service starts
|
|
6289
|
-
* and ends, how many runs there are, which one is next — is derived from the values at render
|
|
6290
|
-
* time, never stored: a "next departure" is wrong the moment it is written down, exactly the
|
|
6291
|
-
* argument `f.age` already makes for an age.
|
|
6292
|
-
*
|
|
6293
|
-
* Storage is a plain multiple `time`, so a field can be switched to this from
|
|
6294
|
-
* `f.time({ multiple: true })` and back with no migration.
|
|
6295
|
-
*
|
|
6296
|
-
* @layer preset
|
|
6297
|
-
* @base time
|
|
6298
|
-
* @prim time
|
|
6299
|
-
* @widget timetable
|
|
6300
|
-
* @example f.timetable({ label: 'mod.fields.weekdays' })
|
|
6301
|
-
*/
|
|
6302
5589
|
function timetable(o) {
|
|
6303
5590
|
return declare("timetable", {
|
|
6304
5591
|
...o,
|
|
@@ -6309,18 +5596,6 @@ registerPreset("timetable", "time", {}, {
|
|
|
6309
5596
|
kind: "timetable",
|
|
6310
5597
|
widget: "timetable"
|
|
6311
5598
|
});
|
|
6312
|
-
/** Age — date whose whole-year age (as of today) renders alongside it, e.g. a birth date.
|
|
6313
|
-
* kind 'age', prim 'date'. The age itself is computed at RENDER time, never stored: unlike a
|
|
6314
|
-
* `mutate`-driven computed field, it must be right on every day that passes, not only the day
|
|
6315
|
-
* the record was last written, so this is display metadata over `date`, the same shape as
|
|
6316
|
-
* `f.reminder`'s own relative-to-today note.
|
|
6317
|
-
*
|
|
6318
|
-
* @layer preset
|
|
6319
|
-
* @base date
|
|
6320
|
-
* @prim date
|
|
6321
|
-
* @widget age
|
|
6322
|
-
* @example f.age({ label: 'mod.fields.birthDate' })
|
|
6323
|
-
*/
|
|
6324
5599
|
function age(o) {
|
|
6325
5600
|
return declare("age", o);
|
|
6326
5601
|
}
|
|
@@ -6331,15 +5606,6 @@ registerPreset("age", "date", {}, {
|
|
|
6331
5606
|
return typeOf("date").build(o, parts);
|
|
6332
5607
|
}
|
|
6333
5608
|
});
|
|
6334
|
-
/**
|
|
6335
|
-
* Percentage 0..100 — real with rules:{min:0,max:100}.
|
|
6336
|
-
*
|
|
6337
|
-
* @layer preset
|
|
6338
|
-
* @base real
|
|
6339
|
-
* @prim number
|
|
6340
|
-
* @widget number
|
|
6341
|
-
* @example f.percent({ label: 'mod.fields.progress' })
|
|
6342
|
-
*/
|
|
6343
5609
|
function percent(o) {
|
|
6344
5610
|
return real({
|
|
6345
5611
|
...o,
|
|
@@ -6350,15 +5616,6 @@ function percent(o) {
|
|
|
6350
5616
|
}
|
|
6351
5617
|
});
|
|
6352
5618
|
}
|
|
6353
|
-
/**
|
|
6354
|
-
* Year — int with rules:{min:1900,max:2100}; bounds can be overridden via rules.min/max.
|
|
6355
|
-
*
|
|
6356
|
-
* @layer preset
|
|
6357
|
-
* @base int
|
|
6358
|
-
* @prim number
|
|
6359
|
-
* @widget number
|
|
6360
|
-
* @example f.year({ label: 'mod.fields.releaseYear' })
|
|
6361
|
-
*/
|
|
6362
5619
|
function year(o) {
|
|
6363
5620
|
return int({
|
|
6364
5621
|
...o,
|
|
@@ -6369,15 +5626,6 @@ function year(o) {
|
|
|
6369
5626
|
}
|
|
6370
5627
|
});
|
|
6371
5628
|
}
|
|
6372
|
-
/**
|
|
6373
|
-
* Weight — always grams (int). kind 'weight', prim 'number'.
|
|
6374
|
-
*
|
|
6375
|
-
* @layer preset
|
|
6376
|
-
* @base int
|
|
6377
|
-
* @prim number
|
|
6378
|
-
* @widget weight
|
|
6379
|
-
* @example f.weight({ label: 'mod.fields.weight' })
|
|
6380
|
-
*/
|
|
6381
5629
|
function weight(o) {
|
|
6382
5630
|
return declare("weight", o);
|
|
6383
5631
|
}
|
|
@@ -6395,12 +5643,6 @@ registerPreset("weight", "int", { config: { min: 1 } }, {
|
|
|
6395
5643
|
};
|
|
6396
5644
|
}
|
|
6397
5645
|
});
|
|
6398
|
-
/**
|
|
6399
|
-
* The three extents of a box, in declaration order — which is also the column order, so
|
|
6400
|
-
* `w` is the magnitude `magnitudeSub()` hands to list sorting and to a filter by field key.
|
|
6401
|
-
* The unit is NOT a role: dimensions are always millimetres, and nothing in the app converts
|
|
6402
|
-
* units (see `docs/decisions.md`, 2026-08-05), so it stays a hint rather than a fourth column.
|
|
6403
|
-
*/
|
|
6404
5646
|
var DIMENSION_ROLES = () => [
|
|
6405
5647
|
role("w", ["number"], "int"),
|
|
6406
5648
|
role("h", ["number"], "int"),
|
|
@@ -6424,27 +5666,9 @@ registerType("dimensions", {
|
|
|
6424
5666
|
structureCode: "dimensions_structure",
|
|
6425
5667
|
hints: () => ({ unit: "mm" })
|
|
6426
5668
|
});
|
|
6427
|
-
/**
|
|
6428
|
-
* Dimensions — box dimensions {w,h,d}, int mm across the real sub-columns `key__w|h|d`.
|
|
6429
|
-
*
|
|
6430
|
-
* @layer preset
|
|
6431
|
-
* @base int
|
|
6432
|
-
* @prim text
|
|
6433
|
-
* @widget dimensions
|
|
6434
|
-
* @example f.dimensions({ label: 'mod.fields.size' })
|
|
6435
|
-
*/
|
|
6436
5669
|
function dimensions(o) {
|
|
6437
5670
|
return declare("dimensions", o);
|
|
6438
5671
|
}
|
|
6439
|
-
/**
|
|
6440
|
-
* Country — a string whose values are the country list, rendered with flags.
|
|
6441
|
-
*
|
|
6442
|
-
* @layer preset
|
|
6443
|
-
* @base string
|
|
6444
|
-
* @prim text
|
|
6445
|
-
* @widget text
|
|
6446
|
-
* @example f.country({ label: 'mod.fields.country' })
|
|
6447
|
-
*/
|
|
6448
5672
|
function country(raw) {
|
|
6449
5673
|
return string({
|
|
6450
5674
|
...raw,
|
|
@@ -6454,22 +5678,6 @@ function country(raw) {
|
|
|
6454
5678
|
noSearch: true
|
|
6455
5679
|
});
|
|
6456
5680
|
}
|
|
6457
|
-
/**
|
|
6458
|
-
* Currency — the ISO 4217 code the record's amounts are in, declared ONCE per record.
|
|
6459
|
-
* The money twin of `f.unit`: an ordinary string over the global `currencies` list, so it
|
|
6460
|
-
* is an ordinary column (groupable, filterable) and its picker is an expanded list rather
|
|
6461
|
-
* than a combobox wedged into some amount's editor.
|
|
6462
|
-
*
|
|
6463
|
-
* A record carries ONE currency. Two amounts on one record that need different currencies
|
|
6464
|
-
* are two records — `multiple: true` here means "the currencies this record deals in"
|
|
6465
|
-
* (what an account holds), and binds no amount.
|
|
6466
|
-
*
|
|
6467
|
-
* @layer preset
|
|
6468
|
-
* @base string
|
|
6469
|
-
* @prim text
|
|
6470
|
-
* @widget text
|
|
6471
|
-
* @example f.currency({ label: 'mod.fields.currency' })
|
|
6472
|
-
*/
|
|
6473
5681
|
function currency(raw) {
|
|
6474
5682
|
return string({
|
|
6475
5683
|
...raw,
|
|
@@ -6479,22 +5687,6 @@ function currency(raw) {
|
|
|
6479
5687
|
noSearch: true
|
|
6480
5688
|
});
|
|
6481
5689
|
}
|
|
6482
|
-
/**
|
|
6483
|
-
* A monetary amount — a plain number in a `real` column, whose currency is a field of the
|
|
6484
|
-
* record (`currencyFrom`). Summable, sortable and filterable by its own name, like any
|
|
6485
|
-
* other number.
|
|
6486
|
-
*
|
|
6487
|
-
* It used to be a composite `{value, currency}`: the amount then lived in `price__value`,
|
|
6488
|
-
* reachable only through `magnitudeSub`, a sum over a collection had to re-assemble
|
|
6489
|
-
* objects in JavaScript, and the currency needed a second control inside the amount's own
|
|
6490
|
-
* editor — the one place a collapsed control must never be (skill field-render).
|
|
6491
|
-
*
|
|
6492
|
-
* @layer preset
|
|
6493
|
-
* @base real
|
|
6494
|
-
* @prim number
|
|
6495
|
-
* @widget money
|
|
6496
|
-
* @example f.money({ label: 'mod.fields.price' })
|
|
6497
|
-
*/
|
|
6498
5690
|
function money(o) {
|
|
6499
5691
|
return declare("money", o);
|
|
6500
5692
|
}
|
|
@@ -6549,60 +5741,17 @@ var presets = {
|
|
|
6549
5741
|
currency,
|
|
6550
5742
|
money,
|
|
6551
5743
|
internalApiToken,
|
|
5744
|
+
internalIdentityLinks,
|
|
6552
5745
|
internalOauthGrants
|
|
6553
5746
|
};
|
|
6554
5747
|
//#endregion
|
|
6555
|
-
//#region ../sdk/
|
|
6556
|
-
/**
|
|
6557
|
-
* Editorial "blocks" — record-page layout sugar over `group()` (bento grids, split
|
|
6558
|
-
* columns, tabs, …), exactly like `row`/`sheet` in fields.ts. Each factory sets
|
|
6559
|
-
* the group's `kind` (renderer resolution — see `elementKind()`) and stashes its own
|
|
6560
|
-
* layout metadata on `ContainerNode.view`, read by the block's own renderer, never by the
|
|
6561
|
-
* generic layout core.
|
|
6562
|
-
*
|
|
6563
|
-
* `calendar`/`journal`/`manifest`/`table` are the one exception to "sugar over a fresh
|
|
6564
|
-
* `group()`": their `source` is already a KEYED, `multiple: true` collection built
|
|
6565
|
-
* elsewhere (`row`/`sheet` in fields.ts — there is no separate factory to DECLARE a
|
|
6566
|
-
* tabular collection), and they stamp `kind`/`view` directly onto THAT group
|
|
6567
|
-
* (`stampCollection`) rather than wrapping it in a new keyless one — see
|
|
6568
|
-
* `stampCollection`'s own comment for why the wrapping shape does not reach the layout
|
|
6569
|
-
* engine's storage-group branch. `table` specifically is a VIEW over a collection built
|
|
6570
|
-
* elsewhere, never a second way to declare one. `calendar`/`journal`/`manifest`/`table`'s
|
|
6571
|
-
* `source` stays typed `ContainerNode` (already built, already keyed) — the exception is about
|
|
6572
|
-
* this wrapping, not about naming, so it is untouched by `SlotInput` below.
|
|
6573
|
-
*
|
|
6574
|
-
* Every OTHER el-valued slot a factory here declares — a single element (`figure.image`,
|
|
6575
|
-
* `masthead.title`, `route.from`, …) or a list (`masthead.meta`, `compare.left`, …) — takes
|
|
6576
|
-
* a NAMED MAP: see `SlotInput`'s own doc comment for the single case, `LayoutInput`
|
|
6577
|
-
* (materialize/decl.ts) for the list case. The slot names the ROLE; the map's own property
|
|
6578
|
-
* name(s) become the STORAGE key(s).
|
|
6579
|
-
*/
|
|
5748
|
+
//#region ../sdk/dist/blocks.js
|
|
6580
5749
|
var isSpanned = (i) => typeof i === "object" && i !== null && "span" in i && "el" in i;
|
|
6581
|
-
/**
|
|
6582
|
-
* Validates a block's single-element slot and hands back the one-entry map it is. A slot
|
|
6583
|
-
* is not built here: every slot a block lays out is merged into ONE map (`merge` below)
|
|
6584
|
-
* and walked by `group()` exactly once, like any other container's `fields`.
|
|
6585
|
-
*
|
|
6586
|
-
* A missing/nullish slot is the same "got 0" failure as an empty map, not a native
|
|
6587
|
-
* TypeError from `Object.keys(undefined)` — every empty-slot failure mode speaks the same
|
|
6588
|
-
* message, whether the author wrote `{}`, left the option out, or passed `undefined`.
|
|
6589
|
-
*/
|
|
6590
5750
|
function slot(block, name, input) {
|
|
6591
5751
|
const count = input == null ? 0 : Object.keys(input).length;
|
|
6592
5752
|
if (count !== 1) throw new Error(`[field.${block}] slot '${name}' expects exactly one entry, got ${count}`);
|
|
6593
5753
|
return input;
|
|
6594
5754
|
}
|
|
6595
|
-
/**
|
|
6596
|
-
* A block's children, in the order the block lays them out. Every slot is a NAMED MAP, so
|
|
6597
|
-
* flattening several slots into one child list is a MERGE: the names the author gave are
|
|
6598
|
-
* already there and nothing has to be generated or derived from a position. Insertion
|
|
6599
|
-
* order is declaration order, which is also the order every ORDERED NAME LIST the layout
|
|
6600
|
-
* metadata below carries (`first`/`second`, `meta`, `conditions`, …) reports its names in —
|
|
6601
|
-
* a NAME, never a count against a flattened position (Task 5).
|
|
6602
|
-
*
|
|
6603
|
-
* A name claimed by two slots is a storage-key collision, not a layout choice — the flat
|
|
6604
|
-
* form used to let both through and store one of them nowhere.
|
|
6605
|
-
*/
|
|
6606
5755
|
function merge(block, ...slots) {
|
|
6607
5756
|
const out = {};
|
|
6608
5757
|
for (const s of slots) {
|
|
@@ -6614,21 +5763,6 @@ function merge(block, ...slots) {
|
|
|
6614
5763
|
}
|
|
6615
5764
|
return out;
|
|
6616
5765
|
}
|
|
6617
|
-
/**
|
|
6618
|
-
* Asymmetric card grid. '1x1' is the implicit default — only wider spans are recorded, BY
|
|
6619
|
-
* NAME (Task 5): each tile IS one declared field, so `spans` is keyed by that field's own
|
|
6620
|
-
* storage name — the same name the renderer already addresses it by — never a position in
|
|
6621
|
-
* the built list.
|
|
6622
|
-
*
|
|
6623
|
-
* `fields` takes a NAMED MAP of `BentoItem` values, handed to `group()` which walks it
|
|
6624
|
-
* exactly like every other container's `fields`.
|
|
6625
|
-
*
|
|
6626
|
-
* @layer block
|
|
6627
|
-
* @base group
|
|
6628
|
-
* @prim —
|
|
6629
|
-
* @widget bento
|
|
6630
|
-
* @example f.bento({ fields: { name: f.string({ label: '…' }), model: { span: '2x1', el: f.string({ label: '…' }) } } })
|
|
6631
|
-
*/
|
|
6632
5766
|
function bento(opts) {
|
|
6633
5767
|
const spans = {};
|
|
6634
5768
|
const fields = Object.fromEntries(Object.entries(opts.fields).map(([name, item]) => {
|
|
@@ -6649,23 +5783,6 @@ function bento(opts) {
|
|
|
6649
5783
|
}
|
|
6650
5784
|
};
|
|
6651
5785
|
}
|
|
6652
|
-
/**
|
|
6653
|
-
* Two-column split (default ratio 1:1). Columns are flattened; the left/right boundary is
|
|
6654
|
-
* remembered.
|
|
6655
|
-
*
|
|
6656
|
-
* Each column is a LIST slot (a named map — see `LayoutInput`'s own doc comment). The
|
|
6657
|
-
* tuple POSITION (index 0/1) stays the left/right layout signal, unrelated to storage
|
|
6658
|
-
* naming; each column's own fields are named the same way `fields` is everywhere else, and
|
|
6659
|
-
* the two columns are MERGED into one map for `group()` (`merge`, above). `view.first`/
|
|
6660
|
-
* `view.second` (Task 5) are the ORDERED LISTS of each column's own field names — the
|
|
6661
|
-
* renderer reaches every child by name, never a count against a flattened position.
|
|
6662
|
-
*
|
|
6663
|
-
* @layer block
|
|
6664
|
-
* @base group
|
|
6665
|
-
* @prim —
|
|
6666
|
-
* @widget split
|
|
6667
|
-
* @example f.split({ columns: [{ left: f.string({ label: '…' }) }, { right: f.string({ label: '…' }) }] })
|
|
6668
|
-
*/
|
|
6669
5786
|
function split(opts) {
|
|
6670
5787
|
return {
|
|
6671
5788
|
...group({
|
|
@@ -6680,14 +5797,6 @@ function split(opts) {
|
|
|
6680
5797
|
}
|
|
6681
5798
|
};
|
|
6682
5799
|
}
|
|
6683
|
-
/** Even N-column grid (default 3).
|
|
6684
|
-
*
|
|
6685
|
-
* @layer block
|
|
6686
|
-
* @base group
|
|
6687
|
-
* @prim —
|
|
6688
|
-
* @widget grid
|
|
6689
|
-
* @example f.grid({ columns: 3, fields: { a: f.string({ label: '…' }), b: f.string({ label: '…' }) } })
|
|
6690
|
-
*/
|
|
6691
5800
|
function grid(opts) {
|
|
6692
5801
|
return {
|
|
6693
5802
|
...group({
|
|
@@ -6702,17 +5811,6 @@ function grid(opts) {
|
|
|
6702
5811
|
}
|
|
6703
5812
|
};
|
|
6704
5813
|
}
|
|
6705
|
-
/**
|
|
6706
|
-
* A visual, an optional pull quote, and supporting facts. `view` names each role (Task 5):
|
|
6707
|
-
* `visual`/`quote` are single field names (`quote` `undefined` when not declared),
|
|
6708
|
-
* `facts` is the ORDERED LIST of the fact fields' own storage names.
|
|
6709
|
-
*
|
|
6710
|
-
* @layer block
|
|
6711
|
-
* @base group
|
|
6712
|
-
* @prim —
|
|
6713
|
-
* @widget spread
|
|
6714
|
-
* @example f.spread({ visual: { photo: f.image({ label: '…' }) }, quote: { quote: f.string({ label: '…' }) }, facts: { fact: f.string({ label: '…' }) } })
|
|
6715
|
-
*/
|
|
6716
5814
|
function spread(opts) {
|
|
6717
5815
|
const visual = slot("spread", "visual", opts.visual);
|
|
6718
5816
|
const quote = opts.quote !== void 0 ? slot("spread", "quote", opts.quote) : void 0;
|
|
@@ -6729,17 +5827,6 @@ function spread(opts) {
|
|
|
6729
5827
|
}
|
|
6730
5828
|
};
|
|
6731
5829
|
}
|
|
6732
|
-
/**
|
|
6733
|
-
* Main content plus a side rail. Flattened main-then-aside; the boundary is remembered.
|
|
6734
|
-
* `view.main`/`view.aside` (Task 5) are the ORDERED LISTS of each region's own field
|
|
6735
|
-
* names — see `split`'s own doc comment for what that means and why.
|
|
6736
|
-
*
|
|
6737
|
-
* @layer block
|
|
6738
|
-
* @base group
|
|
6739
|
-
* @prim —
|
|
6740
|
-
* @widget aside
|
|
6741
|
-
* @example f.aside({ fields: { main: f.string({ label: '…' }) }, aside: { side: f.string({ label: '…' }) } })
|
|
6742
|
-
*/
|
|
6743
5830
|
function aside(opts) {
|
|
6744
5831
|
return {
|
|
6745
5832
|
...group({
|
|
@@ -6753,20 +5840,6 @@ function aside(opts) {
|
|
|
6753
5840
|
}
|
|
6754
5841
|
};
|
|
6755
5842
|
}
|
|
6756
|
-
/**
|
|
6757
|
-
* Tabbed sections. Each tab is a NAMED GROUP of its own fields (Task 5) — `t.name` becomes
|
|
6758
|
-
* the storage key of a NESTED, keyless (`scope: 'hoist'`, storage-transparent — see
|
|
6759
|
-
* `group`'s own `scope` doc comment) group wrapping `t.fields`, so the renderer reaches a
|
|
6760
|
-
* tab's own children by descending into that name (`SlotScope`, `render/slot.tsx`) instead
|
|
6761
|
-
* of slicing a flattened list by a cumulative per-tab count. A duplicate `name` across
|
|
6762
|
-
* tabs is a storage-key collision, exactly like `merge`'s own duplicate-name guard.
|
|
6763
|
-
*
|
|
6764
|
-
* @layer block
|
|
6765
|
-
* @base group
|
|
6766
|
-
* @prim —
|
|
6767
|
-
* @widget tabs
|
|
6768
|
-
* @example f.tabs({ tabs: [{ name: 'overview', label: '…', fields: { a: f.string({ label: '…' }) } }] })
|
|
6769
|
-
*/
|
|
6770
5843
|
function tabs(opts) {
|
|
6771
5844
|
const fields = {};
|
|
6772
5845
|
for (const t of opts.tabs) {
|
|
@@ -6788,17 +5861,6 @@ function tabs(opts) {
|
|
|
6788
5861
|
}
|
|
6789
5862
|
};
|
|
6790
5863
|
}
|
|
6791
|
-
/**
|
|
6792
|
-
* Collapsible sections. Each section is a NAMED GROUP of its own fields (Task 5) — see
|
|
6793
|
-
* `tabs`'s own doc comment for what that means and why; the renderer descends into a
|
|
6794
|
-
* section's own children by name (`SlotScope`) instead of a cumulative count.
|
|
6795
|
-
*
|
|
6796
|
-
* @layer block
|
|
6797
|
-
* @base group
|
|
6798
|
-
* @prim —
|
|
6799
|
-
* @widget accordion
|
|
6800
|
-
* @example f.accordion({ sections: [{ name: 'details', label: '…', fields: { a: f.string({ label: '…' }) } }] })
|
|
6801
|
-
*/
|
|
6802
5864
|
function accordion(opts) {
|
|
6803
5865
|
const fields = {};
|
|
6804
5866
|
for (const s of opts.sections) {
|
|
@@ -6821,16 +5883,6 @@ function accordion(opts) {
|
|
|
6821
5883
|
}
|
|
6822
5884
|
};
|
|
6823
5885
|
}
|
|
6824
|
-
/** Flowing prose paragraph, optionally with a dropped first capital letter on ONE named
|
|
6825
|
-
* field — `dropCap` names WHICH one (Task 5), never "whichever field was declared
|
|
6826
|
-
* first." Must be one of `fields`'s own storage keys.
|
|
6827
|
-
*
|
|
6828
|
-
* @layer block
|
|
6829
|
-
* @base group
|
|
6830
|
-
* @prim —
|
|
6831
|
-
* @widget prose
|
|
6832
|
-
* @example f.prose({ fields: { body: f.text({ label: '…' }) }, dropCap: 'body' })
|
|
6833
|
-
*/
|
|
6834
5886
|
function prose(opts) {
|
|
6835
5887
|
if (opts.dropCap !== void 0 && !(opts.dropCap in opts.fields)) throw new Error(`[field.prose] dropCap '${opts.dropCap}' is not one of this block's own fields`);
|
|
6836
5888
|
return {
|
|
@@ -6846,17 +5898,6 @@ function prose(opts) {
|
|
|
6846
5898
|
}
|
|
6847
5899
|
};
|
|
6848
5900
|
}
|
|
6849
|
-
/** A small labeled plate of fields: one TITLE (large, semibold), then zero or more
|
|
6850
|
-
* SUBTITLE fields (small, tracked-out, muted) beneath it. Which field is the title is
|
|
6851
|
-
* NAMED (Task 5), the same way `figure.image`/`masthead.title` is — never "whichever
|
|
6852
|
-
* field was declared first."
|
|
6853
|
-
*
|
|
6854
|
-
* @layer block
|
|
6855
|
-
* @base group
|
|
6856
|
-
* @prim —
|
|
6857
|
-
* @widget plaque
|
|
6858
|
-
* @example f.plaque({ title: { a: f.string({ label: '…' }) }, subtitle: { b: f.string({ label: '…' }) } })
|
|
6859
|
-
*/
|
|
6860
5901
|
function plaque(opts) {
|
|
6861
5902
|
const title = slot("plaque", "title", opts.title);
|
|
6862
5903
|
return {
|
|
@@ -6873,14 +5914,6 @@ function plaque(opts) {
|
|
|
6873
5914
|
}
|
|
6874
5915
|
};
|
|
6875
5916
|
}
|
|
6876
|
-
/** A ledger-style stack of label/value rows.
|
|
6877
|
-
*
|
|
6878
|
-
* @layer block
|
|
6879
|
-
* @base group
|
|
6880
|
-
* @prim —
|
|
6881
|
-
* @widget ledger
|
|
6882
|
-
* @example f.ledger({ fields: { a: f.string({ label: '…' }) } })
|
|
6883
|
-
*/
|
|
6884
5917
|
function ledger(opts) {
|
|
6885
5918
|
return {
|
|
6886
5919
|
...group({
|
|
@@ -6892,14 +5925,6 @@ function ledger(opts) {
|
|
|
6892
5925
|
view: { kind: "ledger" }
|
|
6893
5926
|
};
|
|
6894
5927
|
}
|
|
6895
|
-
/** A row of headline statistics.
|
|
6896
|
-
*
|
|
6897
|
-
* @layer block
|
|
6898
|
-
* @base group
|
|
6899
|
-
* @prim —
|
|
6900
|
-
* @widget stats
|
|
6901
|
-
* @example f.stats({ fields: { count: f.real({ label: '…' }) } })
|
|
6902
|
-
*/
|
|
6903
5928
|
function stats(opts) {
|
|
6904
5929
|
return {
|
|
6905
5930
|
...group({
|
|
@@ -6911,14 +5936,6 @@ function stats(opts) {
|
|
|
6911
5936
|
view: { kind: "stats" }
|
|
6912
5937
|
};
|
|
6913
5938
|
}
|
|
6914
|
-
/** A cluster of small tag-like facets.
|
|
6915
|
-
*
|
|
6916
|
-
* @layer block
|
|
6917
|
-
* @base group
|
|
6918
|
-
* @prim —
|
|
6919
|
-
* @widget facets
|
|
6920
|
-
* @example f.facets({ fields: { tag: f.string({ label: '…' }) } })
|
|
6921
|
-
*/
|
|
6922
5939
|
function facets(opts) {
|
|
6923
5940
|
return {
|
|
6924
5941
|
...group({
|
|
@@ -6930,14 +5947,6 @@ function facets(opts) {
|
|
|
6930
5947
|
view: { kind: "facets" }
|
|
6931
5948
|
};
|
|
6932
5949
|
}
|
|
6933
|
-
/** A monospace terminal/code block, with an optional i18n caption.
|
|
6934
|
-
*
|
|
6935
|
-
* @layer block
|
|
6936
|
-
* @base group
|
|
6937
|
-
* @prim —
|
|
6938
|
-
* @widget terminal
|
|
6939
|
-
* @example f.terminal({ fields: { log: f.text({ label: '…' }) }, caption: 'mod.blocks.terminalCaption' })
|
|
6940
|
-
*/
|
|
6941
5950
|
function terminal(opts) {
|
|
6942
5951
|
return {
|
|
6943
5952
|
...group({
|
|
@@ -6952,14 +5961,6 @@ function terminal(opts) {
|
|
|
6952
5961
|
}
|
|
6953
5962
|
};
|
|
6954
5963
|
}
|
|
6955
|
-
/** A toned callout box (default tone: note).
|
|
6956
|
-
*
|
|
6957
|
-
* @layer block
|
|
6958
|
-
* @base group
|
|
6959
|
-
* @prim —
|
|
6960
|
-
* @widget callout
|
|
6961
|
-
* @example f.callout({ tone: 'warn', fields: { note: f.string({ label: '…' }) } })
|
|
6962
|
-
*/
|
|
6963
5964
|
function callout(opts) {
|
|
6964
5965
|
return {
|
|
6965
5966
|
...group({
|
|
@@ -6974,17 +5975,6 @@ function callout(opts) {
|
|
|
6974
5975
|
}
|
|
6975
5976
|
};
|
|
6976
5977
|
}
|
|
6977
|
-
/**
|
|
6978
|
-
* Two side-by-side sets of fields. Flattened left-then-right; the boundary is remembered.
|
|
6979
|
-
* `view.left`/`view.right` (Task 5) are the ORDERED LISTS of each column's own field
|
|
6980
|
-
* names — see `split`'s own doc comment for what that means and why.
|
|
6981
|
-
*
|
|
6982
|
-
* @layer block
|
|
6983
|
-
* @base group
|
|
6984
|
-
* @prim —
|
|
6985
|
-
* @widget compare
|
|
6986
|
-
* @example f.compare({ left: { a: f.string({ label: '…' }) }, right: { b: f.string({ label: '…' }) } })
|
|
6987
|
-
*/
|
|
6988
5978
|
function compare(opts) {
|
|
6989
5979
|
return {
|
|
6990
5980
|
...group({
|
|
@@ -7002,20 +5992,6 @@ function compare(opts) {
|
|
|
7002
5992
|
}
|
|
7003
5993
|
};
|
|
7004
5994
|
}
|
|
7005
|
-
/**
|
|
7006
|
-
* A picture and its caption as one typographic unit.
|
|
7007
|
-
*
|
|
7008
|
-
* `view` carries the STORAGE NAME of each role (not a count/boolean, Task 5): `image` is
|
|
7009
|
-
* always declared; `caption` is `undefined` when the slot was never given. The renderer
|
|
7010
|
-
* reaches each child through `useSlot`/`<Slot>` by this name — never by position in
|
|
7011
|
-
* `el.fields`, so reordering how `merge()` (above) assembles the fields never matters.
|
|
7012
|
-
*
|
|
7013
|
-
* @layer block
|
|
7014
|
-
* @base group
|
|
7015
|
-
* @prim —
|
|
7016
|
-
* @widget figure
|
|
7017
|
-
* @example f.figure({ image: { photo: f.image({ label: '…' }) }, caption: { caption: f.string({ label: '…' }) } })
|
|
7018
|
-
*/
|
|
7019
5995
|
function figure(opts) {
|
|
7020
5996
|
const image = slot("figure", "image", opts.image);
|
|
7021
5997
|
const caption = opts.caption !== void 0 ? slot("figure", "caption", opts.caption) : void 0;
|
|
@@ -7031,16 +6007,6 @@ function figure(opts) {
|
|
|
7031
6007
|
}
|
|
7032
6008
|
};
|
|
7033
6009
|
}
|
|
7034
|
-
/**
|
|
7035
|
-
* A quoted source with an optional attribution. `view` names each role (Task 5) — see
|
|
7036
|
-
* `figure`'s own doc comment for what that means and why.
|
|
7037
|
-
*
|
|
7038
|
-
* @layer block
|
|
7039
|
-
* @base group
|
|
7040
|
-
* @prim —
|
|
7041
|
-
* @widget epigraph
|
|
7042
|
-
* @example f.epigraph({ source: { quote: f.text({ label: '…' }) }, attribution: { author: f.string({ label: '…' }) } })
|
|
7043
|
-
*/
|
|
7044
6010
|
function epigraph(opts) {
|
|
7045
6011
|
const source = slot("epigraph", "source", opts.source);
|
|
7046
6012
|
const attribution = opts.attribution !== void 0 ? slot("epigraph", "attribution", opts.attribution) : void 0;
|
|
@@ -7056,17 +6022,6 @@ function epigraph(opts) {
|
|
|
7056
6022
|
}
|
|
7057
6023
|
};
|
|
7058
6024
|
}
|
|
7059
|
-
/**
|
|
7060
|
-
* A masthead header: optional overline, title, and trailing meta fields. `view` names each
|
|
7061
|
-
* role (Task 5) — `meta` is the ORDERED LIST of the meta fields' own storage names (declared
|
|
7062
|
-
* insertion order of `opts.meta`), not a count against a flattened position.
|
|
7063
|
-
*
|
|
7064
|
-
* @layer block
|
|
7065
|
-
* @base group
|
|
7066
|
-
* @prim —
|
|
7067
|
-
* @widget masthead
|
|
7068
|
-
* @example f.masthead({ title: { title: f.string({ label: '…' }) }, meta: { subtitle: f.string({ label: '…' }) } })
|
|
7069
|
-
*/
|
|
7070
6025
|
function masthead(opts) {
|
|
7071
6026
|
const overline = opts.overline !== void 0 ? slot("masthead", "overline", opts.overline) : void 0;
|
|
7072
6027
|
const title = slot("masthead", "title", opts.title);
|
|
@@ -7083,14 +6038,6 @@ function masthead(opts) {
|
|
|
7083
6038
|
}
|
|
7084
6039
|
};
|
|
7085
6040
|
}
|
|
7086
|
-
/** A vertical timeline of dated entries.
|
|
7087
|
-
*
|
|
7088
|
-
* @layer block
|
|
7089
|
-
* @base group
|
|
7090
|
-
* @prim —
|
|
7091
|
-
* @widget timeline
|
|
7092
|
-
* @example f.timeline({ fields: { date: f.date({ label: '…' }) } })
|
|
7093
|
-
*/
|
|
7094
6041
|
function timeline(opts) {
|
|
7095
6042
|
return {
|
|
7096
6043
|
...group({
|
|
@@ -7102,14 +6049,6 @@ function timeline(opts) {
|
|
|
7102
6049
|
view: { kind: "timeline" }
|
|
7103
6050
|
};
|
|
7104
6051
|
}
|
|
7105
|
-
/** A single value counting down (or up) to/from a target, with an optional i18n caption.
|
|
7106
|
-
*
|
|
7107
|
-
* @layer block
|
|
7108
|
-
* @base group
|
|
7109
|
-
* @prim —
|
|
7110
|
-
* @widget countdown
|
|
7111
|
-
* @example f.countdown({ source: { deadline: f.date({ label: '…' }) }, caption: 'mod.blocks.countdownCaption' })
|
|
7112
|
-
*/
|
|
7113
6052
|
function countdown(opts) {
|
|
7114
6053
|
const source = slot("countdown", "source", opts.source);
|
|
7115
6054
|
return {
|
|
@@ -7125,14 +6064,6 @@ function countdown(opts) {
|
|
|
7125
6064
|
}
|
|
7126
6065
|
};
|
|
7127
6066
|
}
|
|
7128
|
-
/** A card deck rendered from a single collection source.
|
|
7129
|
-
*
|
|
7130
|
-
* @layer block
|
|
7131
|
-
* @base group
|
|
7132
|
-
* @prim —
|
|
7133
|
-
* @widget deck
|
|
7134
|
-
* @example f.deck({ source: { cards: f.group({ scope: 'nest', multiple: true, fields: { name: f.string({ label: '…' }) } }) } })
|
|
7135
|
-
*/
|
|
7136
6067
|
function deck(opts) {
|
|
7137
6068
|
const source = slot("deck", "source", opts.source);
|
|
7138
6069
|
return {
|
|
@@ -7147,14 +6078,6 @@ function deck(opts) {
|
|
|
7147
6078
|
}
|
|
7148
6079
|
};
|
|
7149
6080
|
}
|
|
7150
|
-
/** A roster rendered from a single collection source.
|
|
7151
|
-
*
|
|
7152
|
-
* @layer block
|
|
7153
|
-
* @base group
|
|
7154
|
-
* @prim —
|
|
7155
|
-
* @widget people
|
|
7156
|
-
* @example f.people({ source: { members: f.group({ scope: 'nest', multiple: true, fields: { name: f.string({ label: '…' }) } }) } })
|
|
7157
|
-
*/
|
|
7158
6081
|
function people(opts) {
|
|
7159
6082
|
const source = slot("people", "source", opts.source);
|
|
7160
6083
|
return {
|
|
@@ -7169,17 +6092,6 @@ function people(opts) {
|
|
|
7169
6092
|
}
|
|
7170
6093
|
};
|
|
7171
6094
|
}
|
|
7172
|
-
/**
|
|
7173
|
-
* A person/entity identity block: optional avatar, name, optional role, and channel links.
|
|
7174
|
-
* `view` names each role (Task 5): `avatar`/`role` are the declared field's own storage
|
|
7175
|
-
* name or `undefined`; `channels` is the ORDERED LIST of the channel fields' own names.
|
|
7176
|
-
*
|
|
7177
|
-
* @layer block
|
|
7178
|
-
* @base group
|
|
7179
|
-
* @prim —
|
|
7180
|
-
* @widget identity
|
|
7181
|
-
* @example f.identity({ name: { name: f.string({ label: '…' }) }, role: { role: f.string({ label: '…' }) } })
|
|
7182
|
-
*/
|
|
7183
6095
|
function identity(opts) {
|
|
7184
6096
|
const avatar = opts.avatar !== void 0 ? slot("identity", "avatar", opts.avatar) : void 0;
|
|
7185
6097
|
const name = slot("identity", "name", opts.name);
|
|
@@ -7198,25 +6110,6 @@ function identity(opts) {
|
|
|
7198
6110
|
}
|
|
7199
6111
|
};
|
|
7200
6112
|
}
|
|
7201
|
-
/** One measure out of an optional max, from ONE OR MORE named sources, with an optional verdict.
|
|
7202
|
-
*
|
|
7203
|
-
* `source` takes several entries for the same reason `f.identity`'s `channels` does — it is a
|
|
7204
|
-
* role that holds a LIST, so `view.source` is an ordered list of names rather than one name.
|
|
7205
|
-
* Two ratings of the same film out of ten are one measure read twice, and drawing them apart is
|
|
7206
|
-
* what makes them incomparable: the eye has to carry the scale between two figures instead of
|
|
7207
|
-
* reading them against a shared one. `media/title` had exactly that, two `f.score` blocks of
|
|
7208
|
-
* `max: 10` held apart inside an `f.compare`, until this took the restriction off.
|
|
7209
|
-
*
|
|
7210
|
-
* NOT `multiple`. A multiple field is an anonymous array; these are NAMED sources, and the name
|
|
7211
|
-
* is what says which reading came from where. The two are different shapes and the block wants
|
|
7212
|
-
* this one.
|
|
7213
|
-
*
|
|
7214
|
-
* @layer block
|
|
7215
|
-
* @base group
|
|
7216
|
-
* @prim —
|
|
7217
|
-
* @widget score
|
|
7218
|
-
* @example f.score({ source: { imdb: f.real({ label: '…' }), tmdb: f.real({ label: '…' }) }, max: 10 })
|
|
7219
|
-
*/
|
|
7220
6113
|
function score(opts) {
|
|
7221
6114
|
const names = Object.keys(opts.source ?? {});
|
|
7222
6115
|
if (names.length === 0) throw new Error(`[field.score] slot 'source' expects at least one entry, got 0`);
|
|
@@ -7235,14 +6128,6 @@ function score(opts) {
|
|
|
7235
6128
|
}
|
|
7236
6129
|
};
|
|
7237
6130
|
}
|
|
7238
|
-
/** A status value with an optional "since" timestamp.
|
|
7239
|
-
*
|
|
7240
|
-
* @layer block
|
|
7241
|
-
* @base group
|
|
7242
|
-
* @prim —
|
|
7243
|
-
* @widget status
|
|
7244
|
-
* @example f.status({ source: { state: f.string({ label: '…' }) }, since: { since: f.date({ label: '…' }) } })
|
|
7245
|
-
*/
|
|
7246
6131
|
function status(opts) {
|
|
7247
6132
|
const source = slot("status", "source", opts.source);
|
|
7248
6133
|
const since = opts.since !== void 0 ? slot("status", "since", opts.since) : void 0;
|
|
@@ -7259,23 +6144,6 @@ function status(opts) {
|
|
|
7259
6144
|
}
|
|
7260
6145
|
};
|
|
7261
6146
|
}
|
|
7262
|
-
/** A gauge value between an optional min and max, with an optional "of" total.
|
|
7263
|
-
*
|
|
7264
|
-
* `direction` is opt-in: when given, the gauge decides in plain JavaScript whether its own
|
|
7265
|
-
* value crossed the bound that matters (`meterTone`, `blocks/meter.tsx`) and reaches for the
|
|
7266
|
-
* tone palette — the fill, the value text and the `of` companion all move together, never
|
|
7267
|
-
* colour alone (a glyph rides along, see the renderer's own comment). Omit it and nothing
|
|
7268
|
-
* about the gauge changes from today. A `'ceiling'` gauge with no declared `max` (config or
|
|
7269
|
-
* the source field's own hints) resolves it from `of`'s own value instead — a budget's cap is
|
|
7270
|
-
* a per-record field, never a compile-time constant.
|
|
7271
|
-
*
|
|
7272
|
-
* @layer block
|
|
7273
|
-
* @base group
|
|
7274
|
-
* @prim —
|
|
7275
|
-
* @widget meter
|
|
7276
|
-
* @example f.meter({ source: { used: f.real({ label: '…' }) }, min: 0, max: 100 })
|
|
7277
|
-
* @example f.meter({ source: { spent: f.real({ label: '…' }) }, of: { budget: f.real({ label: '…' }) }, direction: 'ceiling' })
|
|
7278
|
-
*/
|
|
7279
6147
|
function meter(opts) {
|
|
7280
6148
|
const source = slot("meter", "source", opts.source);
|
|
7281
6149
|
const of = opts.of !== void 0 ? slot("meter", "of", opts.of) : void 0;
|
|
@@ -7295,16 +6163,6 @@ function meter(opts) {
|
|
|
7295
6163
|
}
|
|
7296
6164
|
};
|
|
7297
6165
|
}
|
|
7298
|
-
/**
|
|
7299
|
-
* A line-item receipt with a trailing total. `view.lines` (Task 5) is the ORDERED LIST of
|
|
7300
|
-
* the line fields' own storage names; `view.total` is the total field's own name.
|
|
7301
|
-
*
|
|
7302
|
-
* @layer block
|
|
7303
|
-
* @base group
|
|
7304
|
-
* @prim —
|
|
7305
|
-
* @widget receipt
|
|
7306
|
-
* @example f.receipt({ fields: { line: f.real({ label: '…' }) }, total: { total: f.real({ label: '…' }) } })
|
|
7307
|
-
*/
|
|
7308
6166
|
function receipt(opts) {
|
|
7309
6167
|
const total = slot("receipt", "total", opts.total);
|
|
7310
6168
|
return {
|
|
@@ -7320,14 +6178,6 @@ function receipt(opts) {
|
|
|
7320
6178
|
}
|
|
7321
6179
|
};
|
|
7322
6180
|
}
|
|
7323
|
-
/** A financial balance value, with an optional i18n caption.
|
|
7324
|
-
*
|
|
7325
|
-
* @layer block
|
|
7326
|
-
* @base group
|
|
7327
|
-
* @prim —
|
|
7328
|
-
* @widget balance
|
|
7329
|
-
* @example f.balance({ source: { balance: f.real({ label: '…' }) }, caption: 'mod.blocks.balanceCaption' })
|
|
7330
|
-
*/
|
|
7331
6181
|
function balance(opts) {
|
|
7332
6182
|
const source = slot("balance", "source", opts.source);
|
|
7333
6183
|
return {
|
|
@@ -7343,21 +6193,6 @@ function balance(opts) {
|
|
|
7343
6193
|
}
|
|
7344
6194
|
};
|
|
7345
6195
|
}
|
|
7346
|
-
/**
|
|
7347
|
-
* A from/to route with optional depart/arrive times and duration. `view` names each role
|
|
7348
|
-
* (Task 5) — see `figure`'s own doc comment for what that means and why.
|
|
7349
|
-
*
|
|
7350
|
-
* @layer block
|
|
7351
|
-
* @base group
|
|
7352
|
-
* @prim —
|
|
7353
|
-
* @widget route
|
|
7354
|
-
* A `stub` is the part of the ticket that is TORN OFF and kept — a seat, a gate, a booking
|
|
7355
|
-
* reference. Declaring one makes the block a ticket rather than a line: the two halves are
|
|
7356
|
-
* separated by a perforation, and the fields in the stub sit below it. Without one the block
|
|
7357
|
-
* is exactly what it was, a route from here to there, so no existing call changes.
|
|
7358
|
-
*
|
|
7359
|
-
* @example f.route({ from: { from: f.string({ label: '…' }) }, to: { to: f.string({ label: '…' }) } })
|
|
7360
|
-
*/
|
|
7361
6196
|
function route(opts) {
|
|
7362
6197
|
const from = slot("route", "from", opts.from);
|
|
7363
6198
|
const to = slot("route", "to", opts.to);
|
|
@@ -7381,25 +6216,6 @@ function route(opts) {
|
|
|
7381
6216
|
}
|
|
7382
6217
|
};
|
|
7383
6218
|
}
|
|
7384
|
-
/**
|
|
7385
|
-
* Stamps a block's `kind` (and, for journal/manifest, its part-key config) directly onto
|
|
7386
|
-
* the collection `source` itself, rather than wrapping it in a keyless layout group.
|
|
7387
|
-
*
|
|
7388
|
-
* The earlier shape (`group({ fields: [source], view: {kind} })`) put `kind` on an outer,
|
|
7389
|
-
* KEYLESS wrapper and left `source` — a KEYED, `multiple: true` collection — as an ordinary
|
|
7390
|
-
* child. `elementKind()` (`web-ui/registry.ts`) resolves a group's `kind` ahead of its
|
|
7391
|
-
* storage kind, so that wrapper WOULD have resolved through the registry — but only the
|
|
7392
|
-
* layout engine's KEYLESS-group dispatch (a declared `kind`, resolved through the field
|
|
7393
|
-
* renderer registry — `layout.tsx`) ever looks at it there; the actual collection one level
|
|
7394
|
-
* down still elementKind()s to the generic 'collection' and renders through the ordinary
|
|
7395
|
-
* CollectionPage widget, opaque to the block.
|
|
7396
|
-
*
|
|
7397
|
-
* Stamping `source` itself instead routes it through the layout engine's STORAGE-group
|
|
7398
|
-
* branch (`isStorageGroup`, `layout.tsx`) exactly like `field.check({multiple:true})`'s
|
|
7399
|
-
* `checklist` kind: an ordinary `registerRenderer({kinds:[...]})` Page receives the raw row
|
|
7400
|
-
* array as `value` and the collection's own write path as `onChange` directly — see
|
|
7401
|
-
* `render/blocks/{calendar,journal,manifest,table}.tsx`.
|
|
7402
|
-
*/
|
|
7403
6219
|
function stampCollection(block, slotted, label, view) {
|
|
7404
6220
|
const [name, source] = Object.entries(slot(block, "source", slotted))[0];
|
|
7405
6221
|
if (!hasChildren(source) || source.scope !== "nest" || source.multiple !== true) throw new Error(`[field.${block}] source must be a collection (a 'nest', multiple:true group)`);
|
|
@@ -7411,45 +6227,12 @@ function stampCollection(block, slotted, label, view) {
|
|
|
7411
6227
|
view
|
|
7412
6228
|
};
|
|
7413
6229
|
}
|
|
7414
|
-
/**
|
|
7415
|
-
* A calendar rendered from a single collection source — see `stampCollection`. `date` is a
|
|
7416
|
-
* part KEY inside each collection row (a `date` or `datetime` field), same as `journal`'s
|
|
7417
|
-
* `date`/`manifest`'s `quantity`/`item` — an explicit key, not a guess: a collection with
|
|
7418
|
-
* more than one date-shaped part (e.g. `start`/`end`) would otherwise mark the grid by
|
|
7419
|
-
* whichever one is found first, silently.
|
|
7420
|
-
*
|
|
7421
|
-
* @layer block
|
|
7422
|
-
* @base group
|
|
7423
|
-
* @prim —
|
|
7424
|
-
* @widget calendar
|
|
7425
|
-
* @example f.calendar({ source: { entries: f.group({ scope: 'nest', multiple: true, fields: { day: f.date({ label: '…' }) } }) }, date: 'day' })
|
|
7426
|
-
*/
|
|
7427
6230
|
function calendar(opts) {
|
|
7428
6231
|
return stampCollection("calendar", opts.source, opts.label, {
|
|
7429
6232
|
kind: "calendar",
|
|
7430
6233
|
date: opts.date
|
|
7431
6234
|
});
|
|
7432
6235
|
}
|
|
7433
|
-
/**
|
|
7434
|
-
* A sparkline over a series held in a collection source — see `stampCollection`. `value`
|
|
7435
|
-
* and `date` are part KEYS inside each collection row, the same explicit addressing
|
|
7436
|
-
* `calendar`'s `date` and `journal`'s `date`/`text` use: a collection with more than one
|
|
7437
|
-
* numeric or date-shaped part would otherwise be charted by whichever one is found first.
|
|
7438
|
-
* `date` orders the series; it is never an axis (the sparkline spaces its points evenly —
|
|
7439
|
-
* `render/blocks/series.ts` says why). `delta: true` adds the change against the previous
|
|
7440
|
-
* reading beside the last value.
|
|
7441
|
-
*
|
|
7442
|
-
* The design sketched this as `f.chart({ source: collectionKey, … })`. There is no
|
|
7443
|
-
* `collectionKey` type and no resolver for one anywhere in the tree — the design's own
|
|
7444
|
-
* "As shipped" note had already retired that sketch for journal/manifest/calendar without
|
|
7445
|
-
* updating this row.
|
|
7446
|
-
*
|
|
7447
|
-
* @layer block
|
|
7448
|
-
* @base group
|
|
7449
|
-
* @prim —
|
|
7450
|
-
* @widget chart
|
|
7451
|
-
* @example f.chart({ source: { readings: f.group({ scope: 'nest', multiple: true, fields: { day: f.date({ label: '…' }), kg: f.real({ label: '…' }) } }) }, value: 'kg', date: 'day', delta: true })
|
|
7452
|
-
*/
|
|
7453
6236
|
function chart(opts) {
|
|
7454
6237
|
return stampCollection("chart", opts.source, opts.label, {
|
|
7455
6238
|
kind: "chart",
|
|
@@ -7458,45 +6241,12 @@ function chart(opts) {
|
|
|
7458
6241
|
delta: opts.delta
|
|
7459
6242
|
});
|
|
7460
6243
|
}
|
|
7461
|
-
/**
|
|
7462
|
-
* A calendar heat map of event DENSITY rendered from a collection source — see
|
|
7463
|
-
* `stampCollection`. `date` is a part KEY inside each collection row, the same explicit
|
|
7464
|
-
* addressing `calendar`'s `date` and `chart`'s `value`/`date` use.
|
|
7465
|
-
*
|
|
7466
|
-
* `heatmap` counts ROWS PER DAY; it reads no value column at all, which is the whole
|
|
7467
|
-
* difference from `chart`: a chart plots what a row measured, a heat map plots how often
|
|
7468
|
-
* rows happened. That is why there is no `value` option here and why one must not be added
|
|
7469
|
-
* as a shortcut for "colour by amount" — a magnitude per day is a different question from a
|
|
7470
|
-
* count per day, and answering it through the same cells would make the picture ambiguous.
|
|
7471
|
-
*
|
|
7472
|
-
* The design sketched this as `f.heatmap({ source: collectionKey, … })`. There is no
|
|
7473
|
-
* `collectionKey` type and no resolver for one anywhere in the tree — the same correction
|
|
7474
|
-
* `f.chart` records above.
|
|
7475
|
-
*
|
|
7476
|
-
* @layer block
|
|
7477
|
-
* @base group
|
|
7478
|
-
* @prim —
|
|
7479
|
-
* @widget heatmap
|
|
7480
|
-
* @example f.heatmap({ source: { sessions: f.group({ scope: 'nest', multiple: true, fields: { day: f.date({ label: '…' }) } }) }, date: 'day' })
|
|
7481
|
-
*/
|
|
7482
6244
|
function heatmap(opts) {
|
|
7483
6245
|
return stampCollection("heatmap", opts.source, opts.label, {
|
|
7484
6246
|
kind: "heatmap",
|
|
7485
6247
|
date: opts.date
|
|
7486
6248
|
});
|
|
7487
6249
|
}
|
|
7488
|
-
/**
|
|
7489
|
-
* A journal-style log rendered from a single collection source — see `stampCollection`.
|
|
7490
|
-
* `date` and `text` are part KEYS inside each collection row, NOT `LayoutEl` positions:
|
|
7491
|
-
* they address parts INSIDE the collection's own rows, which is not a slot a block lays
|
|
7492
|
-
* out itself.
|
|
7493
|
-
*
|
|
7494
|
-
* @layer block
|
|
7495
|
-
* @base group
|
|
7496
|
-
* @prim —
|
|
7497
|
-
* @widget journal
|
|
7498
|
-
* @example f.journal({ source: { entries: f.group({ scope: 'nest', multiple: true, fields: { day: f.date({ label: '…' }), note: f.text({ label: '…' }) } }) }, date: 'day', text: 'note' })
|
|
7499
|
-
*/
|
|
7500
6250
|
function journal(opts) {
|
|
7501
6251
|
return stampCollection("journal", opts.source, opts.label, {
|
|
7502
6252
|
kind: "journal",
|
|
@@ -7504,14 +6254,6 @@ function journal(opts) {
|
|
|
7504
6254
|
text: opts.text
|
|
7505
6255
|
});
|
|
7506
6256
|
}
|
|
7507
|
-
/** A nutrition facts panel, with an optional leading energy value.
|
|
7508
|
-
*
|
|
7509
|
-
* @layer block
|
|
7510
|
-
* @base group
|
|
7511
|
-
* @prim —
|
|
7512
|
-
* @widget nutrition
|
|
7513
|
-
* @example f.nutrition({ fields: { fat: f.real({ label: '…' }) }, energy: { calories: f.real({ label: '…' }) } })
|
|
7514
|
-
*/
|
|
7515
6257
|
function nutrition(opts) {
|
|
7516
6258
|
const energy = opts.energy !== void 0 ? slot("nutrition", "energy", opts.energy) : void 0;
|
|
7517
6259
|
return {
|
|
@@ -7527,17 +6269,6 @@ function nutrition(opts) {
|
|
|
7527
6269
|
}
|
|
7528
6270
|
};
|
|
7529
6271
|
}
|
|
7530
|
-
/**
|
|
7531
|
-
* A specimen card: a title, an optional subtitle, and a set of condition facets. `view`
|
|
7532
|
-
* names each role (Task 5) — `conditions` is the ORDERED LIST of the condition fields' own
|
|
7533
|
-
* storage names, not a count against a flattened position.
|
|
7534
|
-
*
|
|
7535
|
-
* @layer block
|
|
7536
|
-
* @base group
|
|
7537
|
-
* @prim —
|
|
7538
|
-
* @widget specimen
|
|
7539
|
-
* @example f.specimen({ title: { name: f.string({ label: '…' }) }, conditions: { condition: f.string({ label: '…' }) } })
|
|
7540
|
-
*/
|
|
7541
6272
|
function specimen(opts) {
|
|
7542
6273
|
const title = slot("specimen", "title", opts.title);
|
|
7543
6274
|
const subtitle = opts.subtitle !== void 0 ? slot("specimen", "subtitle", opts.subtitle) : void 0;
|
|
@@ -7554,26 +6285,6 @@ function specimen(opts) {
|
|
|
7554
6285
|
}
|
|
7555
6286
|
};
|
|
7556
6287
|
}
|
|
7557
|
-
/**
|
|
7558
|
-
* Measurements against the range they were supposed to fall in, rendered from a single
|
|
7559
|
-
* collection source — see `stampCollection`. `analyte`, `value`, `unit`, `low` and `high` are
|
|
7560
|
-
* part KEYS inside each collection row, NOT `LayoutEl` positions — the same reasoning as
|
|
7561
|
-
* `manifest`'s `quantity`/`item`.
|
|
7562
|
-
*
|
|
7563
|
-
* The point is the COMPARISON. A `f.table` over the same rows prints the bounds as two more
|
|
7564
|
-
* columns and leaves the reader to do it; here each value sits on its own band with the
|
|
7565
|
-
* reference span marked on it, so "outside the range" is seen rather than worked out. That is
|
|
7566
|
-
* also the only thing on the row worth a colour — it is what the reader has to act on.
|
|
7567
|
-
*
|
|
7568
|
-
* `unit`, `low` and `high` are optional: a measurement with no published range (a culture, a
|
|
7569
|
-
* description) still belongs in the same list and simply gets no band.
|
|
7570
|
-
*
|
|
7571
|
-
* @layer block
|
|
7572
|
-
* @base group
|
|
7573
|
-
* @prim —
|
|
7574
|
-
* @widget assay
|
|
7575
|
-
* @example f.assay({ source: { results: f.group({ scope: 'nest', multiple: true, fields: { analyte: f.string({ label: '…' }), value: f.real({ label: '…' }) } }) }, analyte: 'analyte', value: 'value' })
|
|
7576
|
-
*/
|
|
7577
6288
|
function assay(opts) {
|
|
7578
6289
|
return stampCollection("assay", opts.source, opts.label, {
|
|
7579
6290
|
kind: "assay",
|
|
@@ -7584,32 +6295,6 @@ function assay(opts) {
|
|
|
7584
6295
|
...opts.high ? { high: opts.high } : {}
|
|
7585
6296
|
});
|
|
7586
6297
|
}
|
|
7587
|
-
/**
|
|
7588
|
-
* Value moving from one place to another, where the MOVEMENT is the subject — not one more
|
|
7589
|
-
* labelled row among the record's fields. The sum is set large in the machine voice, and the two
|
|
7590
|
-
* ends read as a path beneath it.
|
|
7591
|
-
*
|
|
7592
|
-
* amount — what moved. The one required role, and the reason the block exists
|
|
7593
|
-
* from — where it left. Absent on money that only arrived
|
|
7594
|
-
* to — where it arrived. Absent on money that only left
|
|
7595
|
-
* meta — the record's own remaining fields, under a rule: date, category, reference
|
|
7596
|
-
*
|
|
7597
|
-
* An absent end is not drawn, and that is the whole reading: money that left an account and
|
|
7598
|
-
* arrived nowhere IS an expense. It is read off which ends the record actually has, never
|
|
7599
|
-
* guessed from the shape of a value. What the movement MEANS beyond that — whether this
|
|
7600
|
-
* particular kind of transfer is good news — is carried by the classifier's own option `tone`,
|
|
7601
|
-
* the same declaration every other coloured value in the product uses.
|
|
7602
|
-
*
|
|
7603
|
-
* Distinct from `f.route`, which also has two ends: there the journey is the subject and the
|
|
7604
|
-
* ends are places, so it carries times and a duration; here the ends are accounts and the
|
|
7605
|
-
* subject is the quantity.
|
|
7606
|
-
*
|
|
7607
|
-
* @layer block
|
|
7608
|
-
* @base group
|
|
7609
|
-
* @prim —
|
|
7610
|
-
* @widget flow
|
|
7611
|
-
* @example f.flow({ amount: { amount: f.money({ label: '…' }) }, from: { source: f.relation({ label: '…' }) } })
|
|
7612
|
-
*/
|
|
7613
6298
|
function flow(opts) {
|
|
7614
6299
|
const amount = slot("flow", "amount", opts.amount);
|
|
7615
6300
|
const from = opts.from !== void 0 ? slot("flow", "from", opts.from) : void 0;
|
|
@@ -7629,17 +6314,6 @@ function flow(opts) {
|
|
|
7629
6314
|
}
|
|
7630
6315
|
};
|
|
7631
6316
|
}
|
|
7632
|
-
/**
|
|
7633
|
-
* A packing/cargo manifest rendered from a single collection source — see `stampCollection`.
|
|
7634
|
-
* `quantity` and `item` are part KEYS inside each collection row, NOT `LayoutEl` positions —
|
|
7635
|
-
* the same reasoning as `journal`'s `date`/`text`.
|
|
7636
|
-
*
|
|
7637
|
-
* @layer block
|
|
7638
|
-
* @base group
|
|
7639
|
-
* @prim —
|
|
7640
|
-
* @widget manifest
|
|
7641
|
-
* @example f.manifest({ source: { cargo: f.group({ scope: 'nest', multiple: true, fields: { item: f.string({ label: '…' }), qty: f.real({ label: '…' }) } }) }, quantity: 'qty', item: 'item' })
|
|
7642
|
-
*/
|
|
7643
6317
|
function manifest(opts) {
|
|
7644
6318
|
return stampCollection("manifest", opts.source, opts.label, {
|
|
7645
6319
|
kind: "manifest",
|
|
@@ -7647,26 +6321,6 @@ function manifest(opts) {
|
|
|
7647
6321
|
item: opts.item
|
|
7648
6322
|
});
|
|
7649
6323
|
}
|
|
7650
|
-
/**
|
|
7651
|
-
* A real table over a single collection source — see `stampCollection`.
|
|
7652
|
-
*
|
|
7653
|
-
* A plain collection (`f.row({ key, multiple })`) is a LAYOUT: repeated rows whose columns
|
|
7654
|
-
* line up. A table is an INSTRUMENT: its header sorts, `groupBy` buckets the rows, `totals`
|
|
7655
|
-
* closes it with an aggregate, `numbered` counts them. The server sees the same rows either
|
|
7656
|
-
* way — this factory declares no storage and changes none.
|
|
7657
|
-
*
|
|
7658
|
-
* `sort`, `groupBy` and every key of `totals` name a field of the ROW, exactly as `journal`'s
|
|
7659
|
-
* `date`/`text` and `manifest`'s `quantity`/`item` do: an explicit key, never a guess at which
|
|
7660
|
-
* column looks sortable or summable. `groupBy` needs no categorical filter — the list panel
|
|
7661
|
-
* groups a text field only when `views.groupBy` names it explicitly (`sdk/shelf.ts`), and this
|
|
7662
|
-
* IS that explicit naming.
|
|
7663
|
-
*
|
|
7664
|
-
* @layer block
|
|
7665
|
-
* @base group
|
|
7666
|
-
* @prim —
|
|
7667
|
-
* @widget table
|
|
7668
|
-
* @example f.table({ source: { rows: f.row({ scope: 'nest', multiple: true, fields: { a: f.string({ label: 'mod.fields.a' }) } }) }, sort: 'a' })
|
|
7669
|
-
*/
|
|
7670
6324
|
function table(opts) {
|
|
7671
6325
|
return stampCollection("table", opts.source, opts.label, {
|
|
7672
6326
|
kind: "table",
|
|
@@ -7680,28 +6334,6 @@ function table(opts) {
|
|
|
7680
6334
|
...opts.numbered ? { numbered: true } : {}
|
|
7681
6335
|
});
|
|
7682
6336
|
}
|
|
7683
|
-
/**
|
|
7684
|
-
* An identity document, drawn as the card it is: a caption and its number across the top, a
|
|
7685
|
-
* portrait beside the holder's own fields, the issuing details under a rule, and the
|
|
7686
|
-
* machine-readable strip at the foot. `view` names each role (Task 5) — see `masthead`'s own
|
|
7687
|
-
* doc comment for what an ordered name list means.
|
|
7688
|
-
*
|
|
7689
|
-
* overline — the caption at the top left ("PASSPORT", "DRIVING LICENCE")
|
|
7690
|
-
* number — the document's number, set apart at the top right
|
|
7691
|
-
* photo — the portrait. One field, rendered in a portrait frame beside the body
|
|
7692
|
-
* meta — the card's OWN fields, labelled, at each field's declared span
|
|
7693
|
-
* footer — the fields below the rule: issued, authority, record number
|
|
7694
|
-
* mrz — the machine-readable strip, set in monospace at the foot
|
|
7695
|
-
*
|
|
7696
|
-
* Every role is optional but `number`: a bank card has no `mrz`, a library card no `photo`,
|
|
7697
|
-
* and the card simply omits the part it was given nothing for.
|
|
7698
|
-
*
|
|
7699
|
-
* @layer block
|
|
7700
|
-
* @base group
|
|
7701
|
-
* @prim —
|
|
7702
|
-
* @widget idcard
|
|
7703
|
-
* @example f.idcard({ number: { number: f.string({ label: '…' }) }, meta: { issued: f.string({ label: '…' }) } })
|
|
7704
|
-
*/
|
|
7705
6337
|
function idcard(opts) {
|
|
7706
6338
|
const overline = opts.overline !== void 0 ? slot("idcard", "overline", opts.overline) : void 0;
|
|
7707
6339
|
const number = slot("idcard", "number", opts.number);
|
|
@@ -7723,16 +6355,6 @@ function idcard(opts) {
|
|
|
7723
6355
|
}
|
|
7724
6356
|
};
|
|
7725
6357
|
}
|
|
7726
|
-
/**
|
|
7727
|
-
* Properties inspector — a compact key-value metadata grid (columns default 2).
|
|
7728
|
-
* Formatted pairs with clear labels and concise values (like Notion / Linear property bars).
|
|
7729
|
-
*
|
|
7730
|
-
* @layer block
|
|
7731
|
-
* @base group
|
|
7732
|
-
* @prim —
|
|
7733
|
-
* @widget properties
|
|
7734
|
-
* @example f.properties({ columns: 2, fields: { a: f.string({ label: '…' }) } })
|
|
7735
|
-
*/
|
|
7736
6358
|
function properties(opts) {
|
|
7737
6359
|
return {
|
|
7738
6360
|
...group({
|
|
@@ -7747,14 +6369,6 @@ function properties(opts) {
|
|
|
7747
6369
|
}
|
|
7748
6370
|
};
|
|
7749
6371
|
}
|
|
7750
|
-
/** Vertical stack — 1-column full-width flow of fields.
|
|
7751
|
-
*
|
|
7752
|
-
* @layer block
|
|
7753
|
-
* @base group
|
|
7754
|
-
* @prim —
|
|
7755
|
-
* @widget stack
|
|
7756
|
-
* @example f.stack({ fields: { a: f.string({ label: '…' }) } })
|
|
7757
|
-
*/
|
|
7758
6372
|
function stack(opts) {
|
|
7759
6373
|
return {
|
|
7760
6374
|
...group({
|
|
@@ -7766,7 +6380,6 @@ function stack(opts) {
|
|
|
7766
6380
|
view: { kind: "stack" }
|
|
7767
6381
|
};
|
|
7768
6382
|
}
|
|
7769
|
-
/** The block factories, merged into the `f` namespace beside row/table/sheet — see fields.ts. */
|
|
7770
6383
|
var blocks = {
|
|
7771
6384
|
bento,
|
|
7772
6385
|
split,
|
|
@@ -8550,7 +7163,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
8550
7163
|
};
|
|
8551
7164
|
}));
|
|
8552
7165
|
//#endregion
|
|
8553
|
-
//#region ../sdk/
|
|
7166
|
+
//#region ../sdk/dist/fields/constants.js
|
|
8554
7167
|
var import_src = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
8555
7168
|
var LANGUAGES_LIST = require_data();
|
|
8556
7169
|
var LANGUAGES = {};
|
|
@@ -8606,58 +7219,10 @@ import_src.default.getAllCodes().map((code) => ({
|
|
|
8606
7219
|
name: import_src.default.getNativeName(code)
|
|
8607
7220
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
8608
7221
|
//#endregion
|
|
8609
|
-
//#region ../sdk/
|
|
8610
|
-
/**
|
|
8611
|
-
* The field type system — the heart of the platform. A pure, isomorphic module.
|
|
8612
|
-
* One field description → three consumers:
|
|
8613
|
-
* 1. `zod` — validation (identical on server and client)
|
|
8614
|
-
* 2. `column` — the DB column type
|
|
8615
|
-
* 3. `prim`/`kind` — keys for resolving the renderer (see web/render/registry)
|
|
8616
|
-
*/
|
|
8617
|
-
/**
|
|
8618
|
-
* Does this node carry a value of its own? True for a leaf field, for a static display
|
|
8619
|
-
* value, and for a composite — a composite's parts do not replace its own meta.
|
|
8620
|
-
*/
|
|
7222
|
+
//#region ../sdk/dist/fields.js
|
|
8621
7223
|
var hasValue = (x) => x.type !== void 0 && typeof x.type === "object";
|
|
8622
|
-
/**
|
|
8623
|
-
* Does this node carry children? True for every container and for a composite. An EMPTY
|
|
8624
|
-
* `fields` still counts: a row with no cells is a container that happens to hold nothing,
|
|
8625
|
-
* not a leaf.
|
|
8626
|
-
*/
|
|
8627
7224
|
var hasChildren = (x) => Array.isArray(x.fields);
|
|
8628
|
-
/**
|
|
8629
|
-
* Is this node an ORDINARY NAMED FIELD — the case almost every call site actually means by
|
|
8630
|
-
* "a field", as opposed to a keyless display value (a computed value with no storage) or a
|
|
8631
|
-
* container? `hasValue(x) && !hasChildren(x) && x.key !== undefined` is this exact test,
|
|
8632
|
-
* spelled out by hand at some twenty call sites before this predicate existed.
|
|
8633
|
-
*
|
|
8634
|
-
* That duplication is why this predicate exists: today `hasValue(x) && !hasChildren(x)`
|
|
8635
|
-
* alone answers the same question, because no composite in today's composed schema is a
|
|
8636
|
-
* both-node (see `LayoutNode`'s own doc comment above). The day one exists, a hand-spelled
|
|
8637
|
-
* `hasValue && !hasChildren` silently reclassifies it as a container, while this predicate
|
|
8638
|
-
* — which also checks `key` — still has to be taught what to do with it. One place to fix
|
|
8639
|
-
* instead of twenty. See docs/decisions.md, 2026-08-26, "HAZARD for whoever produces the
|
|
8640
|
-
* first both-node".
|
|
8641
|
-
*/
|
|
8642
7225
|
var isNamedField = (x) => hasValue(x) && !hasChildren(x) && x.key !== void 0;
|
|
8643
|
-
/**
|
|
8644
|
-
* A visual or storage group of fields, nestable to any depth — a group inside a group
|
|
8645
|
-
* inside a group, columns expanding recursively (`key__child__grandchild__…`, see
|
|
8646
|
-
* `checkStorage` in `compose.ts`). There is no level count: the only ceiling is the
|
|
8647
|
-
* identifier budget PostgreSQL imposes on a generated name, checked at composition time
|
|
8648
|
-
* (`MAX_COLUMN_NAME`/`MAX_TABLE_NAME`, `packages/sdk/src/compose.ts`), not a depth guard.
|
|
8649
|
-
* `scope` decides which: `'hoist'` (the default) arranges children inside the PARENT's
|
|
8650
|
-
* scope and stores nothing of its own; `'nest'` owns a scope and is stored —
|
|
8651
|
-
* - embedded (no multiple): a nested object over `key__sub` columns;
|
|
8652
|
-
* - collection (multiple): a child table (array of rows).
|
|
8653
|
-
* A 'nest' group is NAMED BY ITS PARENT — the property it is declared under — and takes
|
|
8654
|
-
* only NAMED children (each with a storage key of its own).
|
|
8655
|
-
*
|
|
8656
|
-
* @layer primitive
|
|
8657
|
-
* @prim —
|
|
8658
|
-
* @widget group
|
|
8659
|
-
* @example f.group({ scope: 'nest', label: 'mod.fields.contact', fields: { name: f.string({}) } })
|
|
8660
|
-
*/
|
|
8661
7226
|
function group(o) {
|
|
8662
7227
|
if (o["view"] !== void 0) throw new Error("[group] `view` is gone — presentational options live in `ui: {}` (see CLAUDE.md § field author options and the field-types skill).");
|
|
8663
7228
|
const r = o.rules ?? {};
|
|
@@ -8678,7 +7243,6 @@ function group(o) {
|
|
|
8678
7243
|
scope,
|
|
8679
7244
|
multiple: o.multiple,
|
|
8680
7245
|
compute: o.value,
|
|
8681
|
-
required: o.required,
|
|
8682
7246
|
unique: r.unique,
|
|
8683
7247
|
label: o.label,
|
|
8684
7248
|
icon: o.icon,
|
|
@@ -8689,21 +7253,6 @@ function group(o) {
|
|
|
8689
7253
|
fields
|
|
8690
7254
|
};
|
|
8691
7255
|
}
|
|
8692
|
-
/**
|
|
8693
|
-
* Layout group, flow nowrap + horizontal scroll (single line). Sugar over group().
|
|
8694
|
-
*
|
|
8695
|
-
* `multiple` is an ordinary modifier here as everywhere else: `f.row({ scope: 'nest',
|
|
8696
|
-
* multiple: true })` is a COLLECTION of rows, and the renderer aligns its columns (a repeated row is what a
|
|
8697
|
-
* table is). There is no separate factory and no separate `display` value to DECLARE a
|
|
8698
|
-
* tabular collection — `f.table` (`blocks.ts`) is a VIEW stamped onto a collection built
|
|
8699
|
-
* here, never a second way to declare one.
|
|
8700
|
-
*
|
|
8701
|
-
* @layer primitive
|
|
8702
|
-
* @prim —
|
|
8703
|
-
* @widget group
|
|
8704
|
-
* @example f.row({ label: 'mod.fields.row', fields: {} })
|
|
8705
|
-
* @example f.row({ scope: 'nest', label: 'mod.fields.items', multiple: true, fields: {} })
|
|
8706
|
-
*/
|
|
8707
7256
|
function row(o) {
|
|
8708
7257
|
return group({
|
|
8709
7258
|
scope: o.scope,
|
|
@@ -8711,27 +7260,10 @@ function row(o) {
|
|
|
8711
7260
|
icon: o.icon,
|
|
8712
7261
|
fields: o.fields,
|
|
8713
7262
|
multiple: o.multiple,
|
|
8714
|
-
required: o.required,
|
|
8715
7263
|
rules: o.rules,
|
|
8716
7264
|
ui: { display: "scroll" }
|
|
8717
7265
|
});
|
|
8718
7266
|
}
|
|
8719
|
-
/**
|
|
8720
|
-
* Layout group with a 2D layout (rows×columns → aligned CSS grid). Sugar over group().
|
|
8721
|
-
*
|
|
8722
|
-
* A ROW IS A GROUP. `fields` is a map of rows, each row a map of its cells: the outer
|
|
8723
|
-
* property names the row, the inner ones name the cells (their storage keys, like any
|
|
8724
|
-
* other named child). Each row materializes into a nested `scope: 'hoist'` group, so a
|
|
8725
|
-
* row boundary is the nesting itself and the renderer reads `group.fields` as its rows
|
|
8726
|
-
* instead of scanning a flat list for separator markers. The cells still belong to the
|
|
8727
|
-
* PARENT's scope — a hoist group owns none — so the columns are exactly the ones the flat
|
|
8728
|
-
* form produced, an empty row included (a row group with no cells).
|
|
8729
|
-
*
|
|
8730
|
-
* @layer primitive
|
|
8731
|
-
* @prim —
|
|
8732
|
-
* @widget group
|
|
8733
|
-
* @example f.sheet({ label: 'mod.fields.sheet', fields: { net: { ip: f.string({}) }, hw: { mac: f.string({}) } } })
|
|
8734
|
-
*/
|
|
8735
7267
|
function sheet(o) {
|
|
8736
7268
|
const rows = Object.fromEntries(Object.entries(o.fields).map(([name, cells]) => [name, group({
|
|
8737
7269
|
scope: "hoist",
|
|
@@ -8744,22 +7276,10 @@ function sheet(o) {
|
|
|
8744
7276
|
ui: { display: "sheet" }
|
|
8745
7277
|
});
|
|
8746
7278
|
}
|
|
8747
|
-
/**
|
|
8748
|
-
* Composite URL — embedded-group sugar. Storage: separate nested columns
|
|
8749
|
-
* (`key__scheme`, `key__host`, `key__port`, …) in the same table. The renderer
|
|
8750
|
-
* is custom (a single link/input, `kind:'url'`) — string↔parts via
|
|
8751
|
-
* parseUrl/buildUrl. Partial/invalid URLs are stored by parts.
|
|
8752
|
-
*
|
|
8753
|
-
* @layer primitive
|
|
8754
|
-
* @prim —
|
|
8755
|
-
* @widget url
|
|
8756
|
-
* @example f.url({ label: 'mod.fields.website' })
|
|
8757
|
-
*/
|
|
8758
7279
|
function url(o) {
|
|
8759
7280
|
return group({
|
|
8760
7281
|
scope: "nest",
|
|
8761
7282
|
label: o.label,
|
|
8762
|
-
required: o.required,
|
|
8763
7283
|
ui: { kind: "url" },
|
|
8764
7284
|
fields: {
|
|
8765
7285
|
scheme: string({}),
|
|
@@ -8773,16 +7293,6 @@ function url(o) {
|
|
|
8773
7293
|
}
|
|
8774
7294
|
});
|
|
8775
7295
|
}
|
|
8776
|
-
/**
|
|
8777
|
-
* Keyed collection: one row per value of a single named field of that row — `by` names
|
|
8778
|
-
* which one (default: the first), and its own name is the row's storage key. See the block
|
|
8779
|
-
* comment above for the full contract (container, fixed, unique).
|
|
8780
|
-
*
|
|
8781
|
-
* @layer primitive
|
|
8782
|
-
* @prim —
|
|
8783
|
-
* @widget group
|
|
8784
|
-
* @example f.keyed({ label: 'mod.fields.translations', fields: { lang: f.string({ options: ['en', 'uk'] }) } })
|
|
8785
|
-
*/
|
|
8786
7296
|
function keyed(o) {
|
|
8787
7297
|
const entries = Object.entries(o.fields);
|
|
8788
7298
|
if (entries.length === 0) throw new Error(`[field.keyed] fields must declare at least the key field`);
|
|
@@ -8811,67 +7321,25 @@ function keyed(o) {
|
|
|
8811
7321
|
}),
|
|
8812
7322
|
scope: "nest",
|
|
8813
7323
|
multiple: true,
|
|
8814
|
-
required: o.required,
|
|
8815
7324
|
unique: o.unique ?? [role],
|
|
8816
7325
|
fixed: o.fixed,
|
|
8817
7326
|
view: { by: role }
|
|
8818
7327
|
};
|
|
8819
7328
|
}
|
|
8820
|
-
var isStorageGroup = (g) => g.scope === "nest";
|
|
8821
7329
|
var isCollectionGroup = (g) => g.scope === "nest" && g.multiple === true;
|
|
8822
7330
|
var isEmbeddedGroup = (g) => g.scope === "nest" && g.multiple !== true;
|
|
8823
|
-
/**
|
|
8824
|
-
* Horizontal divider, optionally labeled (a section heading).
|
|
8825
|
-
*
|
|
8826
|
-
* @layer primitive
|
|
8827
|
-
* @prim —
|
|
8828
|
-
* @widget divider
|
|
8829
|
-
* @example f.divider({ label: 'mod.fields.section' })
|
|
8830
|
-
*/
|
|
8831
7331
|
function divider(o) {
|
|
8832
7332
|
return {
|
|
8833
7333
|
el: "divider",
|
|
8834
7334
|
label: o?.label
|
|
8835
7335
|
};
|
|
8836
7336
|
}
|
|
8837
|
-
/**
|
|
8838
|
-
* Reference markdown block by i18n key.
|
|
8839
|
-
*
|
|
8840
|
-
* The key is resolved WITH the variables of the running instance, so the locale string
|
|
8841
|
-
* may name facts that are not constants: `{{origin}}` is this instance's own address
|
|
8842
|
-
* (`https://coffer.example`), which is how a block documents an endpoint without
|
|
8843
|
-
* freezing a developer's `localhost` into every translation. The set is fixed and
|
|
8844
|
-
* ambient — the block declares only its key. See `web-ui/src/render/info-text.ts`.
|
|
8845
|
-
*
|
|
8846
|
-
* @layer primitive
|
|
8847
|
-
* @prim —
|
|
8848
|
-
* @widget info
|
|
8849
|
-
* @example f.info('mod.fields.infoText')
|
|
8850
|
-
*/
|
|
8851
7337
|
function info(textKey) {
|
|
8852
7338
|
return {
|
|
8853
7339
|
el: "info",
|
|
8854
7340
|
text: textKey
|
|
8855
7341
|
};
|
|
8856
7342
|
}
|
|
8857
|
-
/**
|
|
8858
|
-
* What else points AT this record — the inverse of a relation, which is often the more useful
|
|
8859
|
-
* direction on a record page: a box is more usefully "what is in it" than "what it is in".
|
|
8860
|
-
*
|
|
8861
|
-
* Stores NOTHING and has no column: it declares where to look, and the server answers by asking
|
|
8862
|
-
* the pointing shelf. That is why it is a pseudo-element beside `divider` and `info` rather than
|
|
8863
|
-
* a field — there is no value here to validate or save.
|
|
8864
|
-
*
|
|
8865
|
-
* `from` names the pointing side explicitly (which shelf, and WHICH of its fields), never
|
|
8866
|
-
* "everything that happens to point here": a shelf may point at the same target through two
|
|
8867
|
-
* fields — a transaction has a source account and a destination account — and a panel that
|
|
8868
|
-
* merged them would answer a question nobody asked.
|
|
8869
|
-
*
|
|
8870
|
-
* @layer primitive
|
|
8871
|
-
* @prim —
|
|
8872
|
-
* @widget backrefs
|
|
8873
|
-
* @example f.backrefs({ label: 'mod.fields.storedHere', from: { library: 'things', shelf: 'item', field: 'location' } })
|
|
8874
|
-
*/
|
|
8875
7343
|
function backrefs(o) {
|
|
8876
7344
|
return {
|
|
8877
7345
|
el: "backrefs",
|
|
@@ -8879,14 +7347,6 @@ function backrefs(o) {
|
|
|
8879
7347
|
from: o.from
|
|
8880
7348
|
};
|
|
8881
7349
|
}
|
|
8882
|
-
/**
|
|
8883
|
-
* Action button: invokes the handler registered in actionRegistry under the key `value`.
|
|
8884
|
-
*
|
|
8885
|
-
* @layer primitive
|
|
8886
|
-
* @prim —
|
|
8887
|
-
* @widget button
|
|
8888
|
-
* @example f.button({ label: 'mod.fields.run', value: 'runAction' })
|
|
8889
|
-
*/
|
|
8890
7350
|
function button(o) {
|
|
8891
7351
|
return {
|
|
8892
7352
|
el: "button",
|
|
@@ -8896,38 +7356,14 @@ function button(o) {
|
|
|
8896
7356
|
variant: o.variant
|
|
8897
7357
|
};
|
|
8898
7358
|
}
|
|
8899
|
-
/**
|
|
8900
|
-
* Brand for `fromShelf()` — a private key, never producible by a plain object literal, so
|
|
8901
|
-
* `isShelfSource` tells a dynamic options source apart from a literal array or a named-list
|
|
8902
|
-
* string CHEAPLY (one property read) and WITHOUT guessing from shape (`{library,shelf}`
|
|
8903
|
-
* alone is not enough: a plugin's own option-list item could coincidentally carry those
|
|
8904
|
-
* keys). Not exported — callers only ever get a `ShelfOptionsSource` back from `fromShelf`.
|
|
8905
|
-
*/
|
|
8906
7359
|
var SHELF_SOURCE = Symbol("shelfSource");
|
|
8907
|
-
/** Runtime discriminator for `ShelfOptionsSource` — see `SHELF_SOURCE`'s own doc comment. */
|
|
8908
7360
|
function isShelfSource(v) {
|
|
8909
7361
|
return typeof v === "object" && v !== null && v[SHELF_SOURCE] === true;
|
|
8910
7362
|
}
|
|
8911
7363
|
function resolveDate(v) {
|
|
8912
7364
|
return v === "today" ? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) : v;
|
|
8913
7365
|
}
|
|
8914
|
-
/**
|
|
8915
|
-
* Does this field offer preset values? Not `options.length`: a field whose options come
|
|
8916
|
-
* from a NAMED source carries an empty array until composeRegistry resolves it, and every
|
|
8917
|
-
* consumer that asks the question earlier (a `keyed({fixed})` check at declaration time, a
|
|
8918
|
-
* role's `accepts`) would get the wrong answer.
|
|
8919
|
-
*/
|
|
8920
7366
|
var hasOptions = (f) => (f.options?.length ?? 0) > 0 || f.hints["source"] != null;
|
|
8921
|
-
/**
|
|
8922
|
-
* `hasOptions`'s own companion for phase 1 (a DECLARATION, not a built meta) — the same
|
|
8923
|
-
* question ENUMERATED's `test` asks further down. Now that `options` means the same thing
|
|
8924
|
-
* for every type — a literal list, a named list, or `fromShelf(...)` all describe where
|
|
8925
|
-
* this field's choices come from — the question is exactly "is `options` present?", nothing
|
|
8926
|
-
* more: a `relation` field's choices come from records, which is as much an answer as a
|
|
8927
|
-
* literal array is. No separate `opts.source` to check either: composeRegistry only
|
|
8928
|
-
* resolves the two STATIC forms into values, but `opts.options` itself is set at
|
|
8929
|
-
* declaration time regardless of which form it is.
|
|
8930
|
-
*/
|
|
8931
7367
|
var declHasOptions = (d) => d.opts.options != null;
|
|
8932
7368
|
registerType("string", {
|
|
8933
7369
|
kind: "text",
|
|
@@ -8935,7 +7371,7 @@ registerType("string", {
|
|
|
8935
7371
|
widget: "text",
|
|
8936
7372
|
build(o) {
|
|
8937
7373
|
const cfg = o.config ?? {};
|
|
8938
|
-
const min = cfg.min ??
|
|
7374
|
+
const min = cfg.min ?? 0;
|
|
8939
7375
|
return {
|
|
8940
7376
|
column: "text",
|
|
8941
7377
|
zod: stringContent((str) => {
|
|
@@ -8952,20 +7388,11 @@ registerType("string", {
|
|
|
8952
7388
|
};
|
|
8953
7389
|
}
|
|
8954
7390
|
});
|
|
8955
|
-
/**
|
|
8956
|
-
* Single-line text. Arbitrary pattern/messageKey — via `rules`. Multi-line — `text()`.
|
|
8957
|
-
*
|
|
8958
|
-
* @layer primitive
|
|
8959
|
-
* @prim text
|
|
8960
|
-
* @widget text
|
|
8961
|
-
* @example f.string({ label: 'mod.fields.name' })
|
|
8962
|
-
*/
|
|
8963
7391
|
function string(o) {
|
|
8964
7392
|
return declare("string", o);
|
|
8965
7393
|
}
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
return stringContent((s) => o.required === true ? s.min(1, { message: vmsg("min_length", { min: 1 }) }) : s);
|
|
7394
|
+
function pathZod(_o) {
|
|
7395
|
+
return stringContent((s) => s);
|
|
8969
7396
|
}
|
|
8970
7397
|
registerType("localDir", {
|
|
8971
7398
|
kind: "localDir",
|
|
@@ -8980,14 +7407,6 @@ registerType("localDir", {
|
|
|
8980
7407
|
};
|
|
8981
7408
|
}
|
|
8982
7409
|
});
|
|
8983
|
-
/**
|
|
8984
|
-
* Path to a DIRECTORY on the server (autocomplete, within home only).
|
|
8985
|
-
*
|
|
8986
|
-
* @layer primitive
|
|
8987
|
-
* @prim text
|
|
8988
|
-
* @widget localDir
|
|
8989
|
-
* @example f.localDir({ label: 'mod.fields.path' })
|
|
8990
|
-
*/
|
|
8991
7410
|
function localDir(o) {
|
|
8992
7411
|
return declare("localDir", o);
|
|
8993
7412
|
}
|
|
@@ -9005,14 +7424,6 @@ registerType("localFile", {
|
|
|
9005
7424
|
};
|
|
9006
7425
|
}
|
|
9007
7426
|
});
|
|
9008
|
-
/**
|
|
9009
|
-
* Path to a FILE on the server (autocomplete; config.exts filters extensions).
|
|
9010
|
-
*
|
|
9011
|
-
* @layer primitive
|
|
9012
|
-
* @prim text
|
|
9013
|
-
* @widget localFile
|
|
9014
|
-
* @example f.localFile({ label: 'mod.fields.path' })
|
|
9015
|
-
*/
|
|
9016
7427
|
function localFile(o) {
|
|
9017
7428
|
return declare("localFile", o);
|
|
9018
7429
|
}
|
|
@@ -9024,14 +7435,6 @@ registerType("text", {
|
|
|
9024
7435
|
return typeOf("string").build(o, []);
|
|
9025
7436
|
}
|
|
9026
7437
|
});
|
|
9027
|
-
/**
|
|
9028
|
-
* Multi-line text (`kind: 'textarea'`). No `multiple`.
|
|
9029
|
-
*
|
|
9030
|
-
* @layer primitive
|
|
9031
|
-
* @prim text
|
|
9032
|
-
* @widget textarea
|
|
9033
|
-
* @example f.text({ label: 'mod.fields.notes' })
|
|
9034
|
-
*/
|
|
9035
7438
|
function text(o) {
|
|
9036
7439
|
return declare("text", o);
|
|
9037
7440
|
}
|
|
@@ -9043,17 +7446,6 @@ registerType("i18n", {
|
|
|
9043
7446
|
return typeOf("text").build(o, []);
|
|
9044
7447
|
}
|
|
9045
7448
|
});
|
|
9046
|
-
/**
|
|
9047
|
-
* Translatable text (kind 'i18n'). Stores a string — an i18n key OR a literal.
|
|
9048
|
-
* In view it shows `t(value, { defaultValue: value })`: a known key → translation,
|
|
9049
|
-
* any other text → shown as-is. Edit — raw input (key/literal without t()).
|
|
9050
|
-
* Built-in reference catalogs write a key; user records — a plain literal.
|
|
9051
|
-
*
|
|
9052
|
-
* @layer primitive
|
|
9053
|
-
* @prim text
|
|
9054
|
-
* @widget i18n
|
|
9055
|
-
* @example f.i18n({ label: 'mod.fields.title' })
|
|
9056
|
-
*/
|
|
9057
7449
|
function i18n(o) {
|
|
9058
7450
|
return declare("i18n", o);
|
|
9059
7451
|
}
|
|
@@ -9065,14 +7457,6 @@ registerType("smallText", {
|
|
|
9065
7457
|
return typeOf("text").build(o, []);
|
|
9066
7458
|
}
|
|
9067
7459
|
});
|
|
9068
|
-
/**
|
|
9069
|
-
* Multi-line text, scalar size (narrow column ~2 rows). Full-width — `text()`.
|
|
9070
|
-
*
|
|
9071
|
-
* @layer primitive
|
|
9072
|
-
* @prim text
|
|
9073
|
-
* @widget smalltext
|
|
9074
|
-
* @example f.smallText({ label: 'mod.fields.summary' })
|
|
9075
|
-
*/
|
|
9076
7460
|
function smallText(o) {
|
|
9077
7461
|
return declare("smallText", o);
|
|
9078
7462
|
}
|
|
@@ -9096,14 +7480,6 @@ registerType("real", {
|
|
|
9096
7480
|
};
|
|
9097
7481
|
}
|
|
9098
7482
|
});
|
|
9099
|
-
/**
|
|
9100
|
-
* Real number (column 'real'). min/max/step — via `rules`.
|
|
9101
|
-
*
|
|
9102
|
-
* @layer primitive
|
|
9103
|
-
* @prim number
|
|
9104
|
-
* @widget number
|
|
9105
|
-
* @example f.real({ label: 'mod.fields.weight' })
|
|
9106
|
-
*/
|
|
9107
7483
|
function real(o) {
|
|
9108
7484
|
return declare("real", o);
|
|
9109
7485
|
}
|
|
@@ -9127,14 +7503,6 @@ registerType("int", {
|
|
|
9127
7503
|
};
|
|
9128
7504
|
}
|
|
9129
7505
|
});
|
|
9130
|
-
/**
|
|
9131
|
-
* Integer (column 'integer'). min/max/step — via `rules`.
|
|
9132
|
-
*
|
|
9133
|
-
* @layer primitive
|
|
9134
|
-
* @prim number
|
|
9135
|
-
* @widget number
|
|
9136
|
-
* @example f.int({ label: 'mod.fields.count' })
|
|
9137
|
-
*/
|
|
9138
7506
|
function int(o) {
|
|
9139
7507
|
return declare("int", o);
|
|
9140
7508
|
}
|
|
@@ -9188,14 +7556,6 @@ registerType("date", {
|
|
|
9188
7556
|
};
|
|
9189
7557
|
}
|
|
9190
7558
|
});
|
|
9191
|
-
/**
|
|
9192
|
-
* Date value with configurable granularity ('day' | 'month' | 'year', default 'day').
|
|
9193
|
-
*
|
|
9194
|
-
* @layer primitive
|
|
9195
|
-
* @prim date
|
|
9196
|
-
* @widget date
|
|
9197
|
-
* @example f.date({ label: 'mod.fields.purchaseDate' })
|
|
9198
|
-
*/
|
|
9199
7559
|
function date(o) {
|
|
9200
7560
|
return declare("date", o);
|
|
9201
7561
|
}
|
|
@@ -9217,15 +7577,6 @@ registerType("time", {
|
|
|
9217
7577
|
};
|
|
9218
7578
|
}
|
|
9219
7579
|
});
|
|
9220
|
-
/**
|
|
9221
|
-
* Time-of-day value with configurable granularity ('hour' | 'minute' | 'second', default
|
|
9222
|
-
* 'minute' — the same default `f.datetime` has always had).
|
|
9223
|
-
*
|
|
9224
|
-
* @layer primitive
|
|
9225
|
-
* @prim time
|
|
9226
|
-
* @widget time
|
|
9227
|
-
* @example f.time({ label: 'mod.fields.startTime' })
|
|
9228
|
-
*/
|
|
9229
7580
|
function time(o) {
|
|
9230
7581
|
return declare("time", o);
|
|
9231
7582
|
}
|
|
@@ -9246,14 +7597,6 @@ registerType("datetime", {
|
|
|
9246
7597
|
};
|
|
9247
7598
|
}
|
|
9248
7599
|
});
|
|
9249
|
-
/**
|
|
9250
|
-
* Combined date-and-time value with configurable granularity ('hour' | 'minute' | 'second', default 'minute').
|
|
9251
|
-
*
|
|
9252
|
-
* @layer primitive
|
|
9253
|
-
* @prim datetime
|
|
9254
|
-
* @widget datetime
|
|
9255
|
-
* @example f.datetime({ label: 'mod.fields.startedAt' })
|
|
9256
|
-
*/
|
|
9257
7600
|
function datetime(o) {
|
|
9258
7601
|
return declare("datetime", o);
|
|
9259
7602
|
}
|
|
@@ -9270,15 +7613,6 @@ registerType("boolean", {
|
|
|
9270
7613
|
};
|
|
9271
7614
|
}
|
|
9272
7615
|
});
|
|
9273
|
-
/**
|
|
9274
|
-
* Boolean value rendered as a checkbox; defaults to false, nullable in storage (an
|
|
9275
|
-
* unchanged collection row sends `null`, not `undefined`).
|
|
9276
|
-
*
|
|
9277
|
-
* @layer primitive
|
|
9278
|
-
* @prim checkbox
|
|
9279
|
-
* @widget boolean
|
|
9280
|
-
* @example f.boolean({ label: 'mod.fields.active' })
|
|
9281
|
-
*/
|
|
9282
7616
|
function boolean(o) {
|
|
9283
7617
|
return declare("boolean", o);
|
|
9284
7618
|
}
|
|
@@ -9299,14 +7633,6 @@ registerType("triState", {
|
|
|
9299
7633
|
};
|
|
9300
7634
|
}
|
|
9301
7635
|
});
|
|
9302
|
-
/**
|
|
9303
|
-
* Three-way choice: 'yes' / 'no' / 'unknown'.
|
|
9304
|
-
*
|
|
9305
|
-
* @layer primitive
|
|
9306
|
-
* @prim triState
|
|
9307
|
-
* @widget triState
|
|
9308
|
-
* @example f.triState({ label: 'mod.fields.reviewed' })
|
|
9309
|
-
*/
|
|
9310
7636
|
function triState(o) {
|
|
9311
7637
|
return declare("triState", o);
|
|
9312
7638
|
}
|
|
@@ -9324,60 +7650,12 @@ registerType("stepper", {
|
|
|
9324
7650
|
};
|
|
9325
7651
|
}
|
|
9326
7652
|
});
|
|
9327
|
-
/**
|
|
9328
|
-
* The REACHED stage of an ORDERED set of options. ONE value, so a gap is UNREPRESENTABLE
|
|
9329
|
-
* rather than merely rejected: reaching stage 3 IS stages 1 and 2 having happened, and
|
|
9330
|
-
* "3 but not 2" cannot be written down at all — there is no sequential-gate rule anywhere,
|
|
9331
|
-
* in zod or in the UI, because there is nothing to gate. That is the whole difference from
|
|
9332
|
-
* `f.string({ options, strict })`, whose options are PEERS (a weekday, a category), and
|
|
9333
|
-
* from `f.check`, whose boxes tick independently. An empty value is the legitimate
|
|
9334
|
-
* "not started" state, and going back a stage is legitimate too: the field stores where
|
|
9335
|
-
* the record IS, never a history of how it got there.
|
|
9336
|
-
*
|
|
9337
|
-
* STORAGE — the stage's own VALUE, not its index, and which one it is matters because the
|
|
9338
|
-
* two fail differently. An index survives renaming a stage and breaks on a reorder or an
|
|
9339
|
-
* insertion; a value survives a reorder and breaks on a rename. Inserting a stage into the
|
|
9340
|
-
* middle of the set ('customs' between 'shipped' and 'delivered') is the ORDINARY edit of a
|
|
9341
|
-
* domain that grows, and under index storage it silently reinterprets every row already
|
|
9342
|
-
* stored — corruption with no symptom anywhere. A rename is the rarer edit, it leaves a
|
|
9343
|
-
* value the option set no longer contains, and THAT state is loud at both ends: `strict`
|
|
9344
|
-
* (forced, below) rejects the next write of it, and the renderer draws it as an unknown
|
|
9345
|
-
* stage rather than guessing a position for it or clamping it to a neighbour. A rename is
|
|
9346
|
-
* also the case the tree already has a migration for (`TableOps.fill`/`convert`, skill
|
|
9347
|
-
* `data-migrations`); nothing in it renumbers stored rows after an insertion. So: the
|
|
9348
|
-
* stored value is the option's `value`, and the ORDER is `options`' declaration order.
|
|
9349
|
-
*
|
|
9350
|
-
* What "loud" costs, stated plainly because this field advertises it: `strict` rejects on
|
|
9351
|
-
* WRITE, so a record carrying a stale stage cannot be saved AT ALL until the stage is
|
|
9352
|
-
* repaired — not even for an unrelated edit to another field. That is ordinary strict-field
|
|
9353
|
-
* behaviour rather than anything new here, but on a field whose whole promise is failing
|
|
9354
|
-
* loudly it is the half of the promise that costs something.
|
|
9355
|
-
*
|
|
9356
|
-
* `strict` is forced on and is not the author's to choose — a value outside the ordered set
|
|
9357
|
-
* has no position in it, so it can never be a legitimate entry. Never `multiple`: a record
|
|
9358
|
-
* is at exactly one stage.
|
|
9359
|
-
*
|
|
9360
|
-
* @layer primitive
|
|
9361
|
-
* @prim text
|
|
9362
|
-
* @widget stepper
|
|
9363
|
-
* @example f.stepper({ label: 'mod.fields.stage', options: ['ordered', 'shipped', 'delivered'] })
|
|
9364
|
-
*/
|
|
9365
7653
|
function stepper(o) {
|
|
9366
7654
|
return declare("stepper", {
|
|
9367
7655
|
...o,
|
|
9368
7656
|
strict: true
|
|
9369
7657
|
});
|
|
9370
7658
|
}
|
|
9371
|
-
/**
|
|
9372
|
-
* Reject anything beyond a flat equality map: an operator key (`$in`, `$and`, …), an
|
|
9373
|
-
* object-valued entry (which is how a nested operator like `{ type: { $in: [...] } }`
|
|
9374
|
-
* would smuggle itself in), or an explicit `undefined` value all throw, naming the
|
|
9375
|
-
* offending key. `undefined` is rejected alongside objects because it would otherwise
|
|
9376
|
-
* slip through every later layer unnoticed: `Condition` types it as a valid value,
|
|
9377
|
-
* `conditionKeys` skips it so compose validates nothing against the target shelf, and
|
|
9378
|
-
* the picker's query-string builder turns it into the literal string `'undefined'`
|
|
9379
|
-
* (an empty picker with no error anywhere).
|
|
9380
|
-
*/
|
|
9381
7659
|
function assertFlatEqualityFilter(filter) {
|
|
9382
7660
|
for (const [key, value] of Object.entries(filter)) {
|
|
9383
7661
|
if (key.startsWith("$")) throw new Error(`[relation] filter key '${key}' is an operator — only flat equality is allowed here`);
|
|
@@ -9420,17 +7698,6 @@ registerType("relation", {
|
|
|
9420
7698
|
};
|
|
9421
7699
|
}
|
|
9422
7700
|
});
|
|
9423
|
-
/**
|
|
9424
|
-
* Link to records of another shelf (`{library, shelf}`). Stores an integer id (single)
|
|
9425
|
-
* or a JSON array of integer ids (multiple). Rendered as RecordInline + a record picker.
|
|
9426
|
-
* This used to be the record branch of `select()`; split out into a separate primitive
|
|
9427
|
-
* so select stays a pure enum/source list.
|
|
9428
|
-
*
|
|
9429
|
-
* @layer primitive
|
|
9430
|
-
* @prim relation
|
|
9431
|
-
* @widget relation
|
|
9432
|
-
* @example f.relation({ label: 'mod.fields.owner', options: fromShelf({ library: 'people', shelf: 'person' }) })
|
|
9433
|
-
*/
|
|
9434
7701
|
function relation(o) {
|
|
9435
7702
|
return declare("relation", o);
|
|
9436
7703
|
}
|
|
@@ -9457,49 +7724,13 @@ registerType("json", {
|
|
|
9457
7724
|
};
|
|
9458
7725
|
}
|
|
9459
7726
|
});
|
|
9460
|
-
/**
|
|
9461
|
-
* Arbitrary JSON value: a native object/array, or a string parsed as JSON.
|
|
9462
|
-
*
|
|
9463
|
-
* @layer primitive
|
|
9464
|
-
* @prim json
|
|
9465
|
-
* @widget json
|
|
9466
|
-
* @example f.json({ label: 'mod.fields.raw' })
|
|
9467
|
-
*/
|
|
9468
7727
|
function json(o) {
|
|
9469
7728
|
return declare("json", o);
|
|
9470
7729
|
}
|
|
9471
|
-
/**
|
|
9472
|
-
* A content role accepts whatever the author declared, because for `check` the author's
|
|
9473
|
-
* field IS the filling: there is no second override channel to correct one AGAINST (see
|
|
9474
|
-
* `contentRole` below — both branches of its `normalize` hand back the same declaration).
|
|
9475
|
-
*
|
|
9476
|
-
* It used to demand a field owning ONE column, on the grounds that "a composite inside a
|
|
9477
|
-
* composite would need `key__sub__sub`, which nothing on the flatten/nest path builds".
|
|
9478
|
-
* `partColumns` builds it now, and the flatten/nest walks follow it — so the ground is
|
|
9479
|
-
* gone and with it the only thing this role ever refused.
|
|
9480
|
-
*/
|
|
9481
7730
|
var ANY_CONTENT = {
|
|
9482
7731
|
test: () => true,
|
|
9483
7732
|
describe: "the author's own content field"
|
|
9484
7733
|
};
|
|
9485
|
-
/**
|
|
9486
|
-
* A single check's roles are not a fixed table: they come from the author's own `fields`
|
|
9487
|
-
* and `slots`, the way `measured`'s unit role comes from its `units`. There is no second
|
|
9488
|
-
* override channel: `fields` IS how an author fills the content roles, so every role
|
|
9489
|
-
* resolves through its own `fallback` and `check` takes no `parts` spec. Reusing the
|
|
9490
|
-
* author's `FieldMeta` as that fallback is safe for the same reason — the node was
|
|
9491
|
-
* built inside this one `f.check(…)` call and is shared with nothing else.
|
|
9492
|
-
*
|
|
9493
|
-
* The two forms use DIFFERENT role orders, both load-bearing — `roles(opts)` reads
|
|
9494
|
-
* `opts.multiple` (declared data, already resolved to a concrete boolean for THIS
|
|
9495
|
-
* declaration by the time the pipeline calls it once) and returns the matching order, the
|
|
9496
|
-
* same way `numberRange`/`realRange`'s own `roleZod` reads `opts.config`:
|
|
9497
|
-
* - single: content first, then one boolean per slot — declaration order is column
|
|
9498
|
-
* order, so the leading content field is the magnitude `magnitudeSub()` sorts and
|
|
9499
|
-
* filters by;
|
|
9500
|
-
* - multiple: checkbox columns first, then content — its own established child-table
|
|
9501
|
-
* shape (the renderer's checklist reads the toggles as the row's leading columns).
|
|
9502
|
-
*/
|
|
9503
7734
|
var contentRole = (f) => {
|
|
9504
7735
|
const def = {
|
|
9505
7736
|
role: f.key,
|
|
@@ -9516,55 +7747,25 @@ var checkboxRole = (label, i) => {
|
|
|
9516
7747
|
};
|
|
9517
7748
|
return def;
|
|
9518
7749
|
};
|
|
9519
|
-
/** `opts.slots`, defaulted to one unnamed checkbox — read the same way by `roles`/`roleZod`/`hints`. */
|
|
9520
7750
|
function checkSlots(opts) {
|
|
9521
7751
|
const slots = opts.slots;
|
|
9522
7752
|
return slots && slots.length > 0 ? slots : [""];
|
|
9523
7753
|
}
|
|
9524
|
-
/**
|
|
9525
|
-
* The content fields of one `f.check(…)` call: materialized, and validated at `roles()`
|
|
9526
|
-
* time — declaration-time-equivalent, since every real entry point (`defineShelf`, `group()`,
|
|
9527
|
-
* …) materializes synchronously at shelf-definition time — no duplicate key, no
|
|
9528
|
-
* `check\d+`-shaped key (reserved for the checkbox slots).
|
|
9529
|
-
*
|
|
9530
|
-
* A composite content field is ordinary now: `partColumns` expands it into
|
|
9531
|
-
* `<content>__<sub>` and the single form stores it across `<field>__<content>__<sub>`,
|
|
9532
|
-
* exactly as the multiple form has always stored it in its child table. The guard that
|
|
9533
|
-
* refused one in the single form is gone with the flat collector that made it necessary.
|
|
9534
|
-
*/
|
|
9535
7754
|
function checkContentFields(opts) {
|
|
9536
7755
|
const fields = opts.fields;
|
|
9537
7756
|
const contentFields = materializeTree(fields !== void 0 && Object.keys(fields).length > 0 ? fields : { text: string({ label: "core.fields.text" }) });
|
|
9538
7757
|
for (const f of contentFields) if (/^check\d+$/.test(f.key)) throw new Error(`[field.check] content field '${f.key}' is reserved for checkbox slots`);
|
|
9539
7758
|
return contentFields;
|
|
9540
7759
|
}
|
|
9541
|
-
/** `check`'s roles, in the order its CURRENT form (single vs multiple) stores them — see
|
|
9542
|
-
* this section's own header note for why the order differs and why reading `opts.multiple`
|
|
9543
|
-
* here is enough: one `materialize()` call means one already-resolved `multiple`. This is
|
|
9544
|
-
* the ONE call `checkContentFields` gets per build — the pipeline calls `roles(opts)`
|
|
9545
|
-
* once and resolves the result into `parts`, which `roleZod`/`hints` below read back
|
|
9546
|
-
* instead of recomputing. */
|
|
9547
7760
|
function checkRoles(opts) {
|
|
9548
7761
|
const contentRoles = checkContentFields(opts).map(contentRole);
|
|
9549
7762
|
const checkboxRoles = checkSlots(opts).map(checkboxRole);
|
|
9550
7763
|
return opts.multiple ? [...checkboxRoles, ...contentRoles] : [...contentRoles, ...checkboxRoles];
|
|
9551
7764
|
}
|
|
9552
|
-
/**
|
|
9553
|
-
* The CONTENT slice of `check`'s own already-resolved `parts` — everything but the
|
|
9554
|
-
* checkbox roles `roleZod`/`hints` handle separately (the checkbox roles' default zod is
|
|
9555
|
-
* a bare `z.boolean()` with no content behind it; `hints.fields` is documented content-
|
|
9556
|
-
* only). Sliced by the checkbox COUNT (`checkSlots`, declared data, no materialize), off
|
|
9557
|
-
* whichever end `checkRoles` put them on for this form — not a role-name pattern match,
|
|
9558
|
-
* which would re-hardcode the very `check\d+` shape `checkContentFields` already guards as
|
|
9559
|
-
* a RESERVED key, not a way to recognize one.
|
|
9560
|
-
*/
|
|
9561
7765
|
function checkContentParts(opts, parts) {
|
|
9562
7766
|
const slotCount = checkSlots(opts).length;
|
|
9563
7767
|
return opts.multiple ? parts.slice(slotCount) : parts.slice(0, parts.length - slotCount);
|
|
9564
7768
|
}
|
|
9565
|
-
/** `check`'s per-role default zod, from the parts the pipeline already resolved — reading
|
|
9566
|
-
* `p.meta.zod`/`p.role` off each content part instead of re-materializing the author's
|
|
9567
|
-
* `fields` a second time (see `checkContentParts`). */
|
|
9568
7769
|
function checkRoleZod(opts, parts) {
|
|
9569
7770
|
const shape = {};
|
|
9570
7771
|
for (const p of checkContentParts(opts, parts)) shape[p.role] = p.meta.zod;
|
|
@@ -9587,47 +7788,22 @@ registerType("check", {
|
|
|
9587
7788
|
}))
|
|
9588
7789
|
})
|
|
9589
7790
|
});
|
|
9590
|
-
/**
|
|
9591
|
-
* Row(s) of N labeled checkboxes plus content fields. Single (non-multiple) stores real
|
|
9592
|
-
* columns (`check0`, `check1`, …) alongside the content fields; multiple stores a child
|
|
9593
|
-
* table with the checkbox columns first, then the content fields.
|
|
9594
|
-
*
|
|
9595
|
-
* @layer primitive
|
|
9596
|
-
* @prim check
|
|
9597
|
-
* @widget check
|
|
9598
|
-
* @example f.check({ label: 'mod.fields.tasks' })
|
|
9599
|
-
*/
|
|
9600
7791
|
function check(o) {
|
|
9601
7792
|
return declare("check", o);
|
|
9602
7793
|
}
|
|
9603
|
-
/**
|
|
9604
|
-
* A role whose filling must be ENUMERATED: a unit, a currency. Now that `options` means the
|
|
9605
|
-
* same thing for every type — a literal list, a named list, or `fromShelf(...)` all describe
|
|
9606
|
-
* where this field's choices come from — the question is exactly "is `options` present?": a
|
|
9607
|
-
* `relation` filling qualifies exactly like a select does, its choices being its target
|
|
9608
|
-
* shelf's records rather than a literal list.
|
|
9609
|
-
*
|
|
9610
|
-
* Tests the DECLARED opts (`d.opts.options`), not `hasOptions` on a BUILT meta: a named
|
|
9611
|
-
* option source is bound later, at `composeRegistry`, so asking `hasOptions` here — in
|
|
9612
|
-
* phase 1, before that binding — would read an empty array and reject a perfectly good
|
|
9613
|
-
* named-source declaration.
|
|
9614
|
-
*/
|
|
9615
7794
|
var ENUMERATED = {
|
|
9616
7795
|
test: (d) => d.opts.options != null,
|
|
9617
7796
|
describe: "a field with options, or a relation"
|
|
9618
7797
|
};
|
|
9619
|
-
/** Roles of `measured`: the magnitude and its unit. The unit's default depends on THIS
|
|
9620
|
-
* field's own `units`, so it writes its own `normalize` rather than using `role()`. */
|
|
9621
7798
|
var MEASURED_ROLES = (opts) => {
|
|
9622
7799
|
const unitRole = {
|
|
9623
7800
|
role: "unit",
|
|
9624
7801
|
accepts: ENUMERATED,
|
|
9625
7802
|
normalize: (d) => {
|
|
9626
7803
|
if (d && accepts(unitRole, d)) return d;
|
|
9627
|
-
const { label,
|
|
7804
|
+
const { label, value } = d?.opts ?? {};
|
|
9628
7805
|
return declare("string", {
|
|
9629
7806
|
label,
|
|
9630
|
-
required,
|
|
9631
7807
|
value,
|
|
9632
7808
|
noSearch: d?.opts.noSearch,
|
|
9633
7809
|
ui: uiOf(d?.opts),
|
|
@@ -9638,8 +7814,6 @@ var MEASURED_ROLES = (opts) => {
|
|
|
9638
7814
|
};
|
|
9639
7815
|
return [role("value", ["number"], "real"), unitRole];
|
|
9640
7816
|
};
|
|
9641
|
-
/** `measured`'s per-role default zod: the magnitude (int via step:1, kept for compat) and
|
|
9642
|
-
* the unit, validated against `opts.units`. */
|
|
9643
7817
|
function measuredRoleZod(opts) {
|
|
9644
7818
|
const cfg = opts.config ?? {};
|
|
9645
7819
|
const units = resolveUnits(opts.units);
|
|
@@ -9652,14 +7826,6 @@ function measuredRoleZod(opts) {
|
|
|
9652
7826
|
unit: string$1().refine((v) => units.some((u) => u.value === v), { message: vmsg("measured_unit") })
|
|
9653
7827
|
};
|
|
9654
7828
|
}
|
|
9655
|
-
/**
|
|
9656
|
-
* `measured`'s own structure-failure report: not a flat code, but a SEARCH of the failed
|
|
9657
|
-
* `rowSchema`'s own issues for one that already carries a vmsg-coded message (the unit
|
|
9658
|
-
* role's own `measured_unit` refine) — that message is forwarded verbatim instead of the
|
|
9659
|
-
* flat `measured_structure` code, so the caller learns WHICH part failed. Falls back to
|
|
9660
|
-
* `measured_structure` when no such issue is found (a genuinely malformed row, not a bad
|
|
9661
|
-
* unit).
|
|
9662
|
-
*/
|
|
9663
7829
|
function measuredStructureCode(error) {
|
|
9664
7830
|
const vmsgIssue = error.issues.find((iss) => {
|
|
9665
7831
|
try {
|
|
@@ -9688,15 +7854,6 @@ registerType("measured", {
|
|
|
9688
7854
|
};
|
|
9689
7855
|
}
|
|
9690
7856
|
});
|
|
9691
|
-
/**
|
|
9692
|
-
* Magnitude + per-row unit stored together as a composite {value, unit}; the unit is
|
|
9693
|
-
* validated against `units`.
|
|
9694
|
-
*
|
|
9695
|
-
* @layer primitive
|
|
9696
|
-
* @prim measured
|
|
9697
|
-
* @widget measured
|
|
9698
|
-
* @example f.measured({ label: 'mod.fields.weight', units: 'weight' })
|
|
9699
|
-
*/
|
|
9700
7857
|
function measured(o) {
|
|
9701
7858
|
return declare("measured", o);
|
|
9702
7859
|
}
|
|
@@ -9719,16 +7876,6 @@ registerType("unit", {
|
|
|
9719
7876
|
};
|
|
9720
7877
|
}
|
|
9721
7878
|
});
|
|
9722
|
-
/**
|
|
9723
|
-
* Declares the unit ONCE per record. Every `amount` below it is a plain number on
|
|
9724
|
-
* this scale, so sums and comparisons are ordinary SQL. Deliberately not a
|
|
9725
|
-
* conversion mechanism: units.ts holds labels, not factors.
|
|
9726
|
-
*
|
|
9727
|
-
* @layer primitive
|
|
9728
|
-
* @prim text
|
|
9729
|
-
* @widget unit
|
|
9730
|
-
* @example f.unit({ label: 'mod.fields.unit', options: 'weight' })
|
|
9731
|
-
*/
|
|
9732
7879
|
function unit(o) {
|
|
9733
7880
|
return declare("unit", o);
|
|
9734
7881
|
}
|
|
@@ -9755,20 +7902,9 @@ registerType("amount", {
|
|
|
9755
7902
|
};
|
|
9756
7903
|
}
|
|
9757
7904
|
});
|
|
9758
|
-
/**
|
|
9759
|
-
* A plain number whose unit is declared elsewhere (`unitFrom`). Stored in a `real`
|
|
9760
|
-
* column: summable, comparable and orderable in SQL, unlike `measured`, which pairs
|
|
9761
|
-
* a number with a per-row unit across two sub-columns.
|
|
9762
|
-
*
|
|
9763
|
-
* @layer primitive
|
|
9764
|
-
* @prim number
|
|
9765
|
-
* @widget amount
|
|
9766
|
-
* @example f.amount({ label: 'mod.fields.quantity', unitFrom: 'unit' })
|
|
9767
|
-
*/
|
|
9768
7905
|
function amount(o) {
|
|
9769
7906
|
return declare("amount", o);
|
|
9770
7907
|
}
|
|
9771
|
-
/** Roles of `geo`: the coordinate pair and an optional human label. */
|
|
9772
7908
|
var GEO_ROLES = () => [
|
|
9773
7909
|
role("lat", ["number"], "real"),
|
|
9774
7910
|
role("lng", ["number"], "real"),
|
|
@@ -9786,32 +7922,11 @@ registerType("geo", {
|
|
|
9786
7922
|
label: string$1().nullish()
|
|
9787
7923
|
}),
|
|
9788
7924
|
structureCode: "geo_structure",
|
|
9789
|
-
|
|
7925
|
+
acceptsEmptyRow: true
|
|
9790
7926
|
});
|
|
9791
|
-
/**
|
|
9792
|
-
* Geographic point composite {lat, lng, label?}; lat/lng validated to valid coordinate
|
|
9793
|
-
* ranges.
|
|
9794
|
-
*
|
|
9795
|
-
* @layer primitive
|
|
9796
|
-
* @prim geo
|
|
9797
|
-
* @widget geo
|
|
9798
|
-
* @example f.geo({ label: 'mod.fields.location' })
|
|
9799
|
-
*/
|
|
9800
7927
|
function geo(o) {
|
|
9801
7928
|
return declare("geo", o);
|
|
9802
7929
|
}
|
|
9803
|
-
/**
|
|
9804
|
-
* Roles of `illustrated`: the prose and the picture that illustrates it.
|
|
9805
|
-
*
|
|
9806
|
-
* ORDER IS LOAD-BEARING. `partColumns` emits columns in role order and `magnitudeSub`
|
|
9807
|
-
* (sdk/shelf.ts) takes the first one, so it decides what `?about=…` filters and what a list
|
|
9808
|
-
* column sorts by. `text` first points both at the prose; `image` first would point them at a
|
|
9809
|
-
* file name.
|
|
9810
|
-
*
|
|
9811
|
-
* Both roles use a PREDICATE rather than a prim list: "renders as a picture" and "is prose" are
|
|
9812
|
-
* properties of the field's `kind`, not of its storage family — every file field stores JSON and
|
|
9813
|
-
* every text field stores `text`, so a prim list could not tell an image from a PDF.
|
|
9814
|
-
*/
|
|
9815
7930
|
var ILLUSTRATED_IMAGE_KINDS = /* @__PURE__ */ new Set([
|
|
9816
7931
|
"image",
|
|
9817
7932
|
"media",
|
|
@@ -9832,23 +7947,6 @@ var ILLUSTRATED_ROLES = () => [role("text", {
|
|
|
9832
7947
|
test: (d) => ILLUSTRATED_IMAGE_KINDS.has(typeOf(d.factory).kind),
|
|
9833
7948
|
describe: "an image field (image, media, avatar, cover, poster)"
|
|
9834
7949
|
}, "image")];
|
|
9835
|
-
/**
|
|
9836
|
-
* A composite's per-role default zod, read from each role's OWN fallback filling instead of
|
|
9837
|
-
* being hand-written per type — `partsRowShape` substitutes a part's zod only when the
|
|
9838
|
-
* author OVERRODE the filling (`p.overridden`), so whatever this returns is the entire
|
|
9839
|
-
* validation a DEFAULT filling gets. Hand-writing it is how `illustrated`'s picture role
|
|
9840
|
-
* ended up with `z.unknown()`: a bare `f.illustrated({})`, the factory's own @example,
|
|
9841
|
-
* accepted any garbage in `image` and lost whatever rules the prose filling declares.
|
|
9842
|
-
* Reading the fallbacks makes the two agree by construction, the way `f.period` builds its
|
|
9843
|
-
* own default shape from its resolved sub-fields. Each fallback's zod is already
|
|
9844
|
-
* `optionalize(…, false)`, which maps `''`/`null` to `undefined` — the nullish tolerance the
|
|
9845
|
-
* previous literal spelled out by hand.
|
|
9846
|
-
*
|
|
9847
|
-
* Shared by every composite whose default schema needs nothing from the call's own opts
|
|
9848
|
-
* (`illustrated`, `attachment`) — a type whose per-role shape IS derived from its opts
|
|
9849
|
-
* (`period`'s granularity) or from the resolved fillings (`check`'s content roles) builds
|
|
9850
|
-
* its own instead.
|
|
9851
|
-
*/
|
|
9852
7950
|
function defaultRoleZod(roles) {
|
|
9853
7951
|
const shape = {};
|
|
9854
7952
|
for (const r of roles()) {
|
|
@@ -9864,42 +7962,13 @@ registerType("illustrated", {
|
|
|
9864
7962
|
roles: ILLUSTRATED_ROLES,
|
|
9865
7963
|
roleZod: () => defaultRoleZod(ILLUSTRATED_ROLES),
|
|
9866
7964
|
structureCode: "illustrated_structure",
|
|
9867
|
-
|
|
7965
|
+
acceptsEmptyRow: true,
|
|
9868
7966
|
collectionView: "illustratedList",
|
|
9869
7967
|
hints: (o) => o.config ? { config: o.config } : {}
|
|
9870
7968
|
});
|
|
9871
|
-
/**
|
|
9872
|
-
* A picture and its description as ONE value: `{ text, image }`, stored across
|
|
9873
|
-
* `<key>__text` / `<key>__image`. The renderer wraps the prose around the picture, so a
|
|
9874
|
-
* record page states "what this is, shown and told" as one editorial unit instead of two
|
|
9875
|
-
* fields with an empty rectangle between them.
|
|
9876
|
-
*
|
|
9877
|
-
* `multiple: true` is the ordinary modifier and takes the collection-group path, exactly as
|
|
9878
|
-
* `f.period` and `f.check` do — a child table whose columns are the resolved parts.
|
|
9879
|
-
*
|
|
9880
|
-
* @layer primitive
|
|
9881
|
-
* @prim illustrated
|
|
9882
|
-
* @widget illustrated
|
|
9883
|
-
* @example f.illustrated({ label: 'mod.fields.about' })
|
|
9884
|
-
*/
|
|
9885
7969
|
function illustrated(o) {
|
|
9886
7970
|
return declare("illustrated", o);
|
|
9887
7971
|
}
|
|
9888
|
-
/**
|
|
9889
|
-
* Roles of `attachment`: the name the owner gives the document, and the document itself.
|
|
9890
|
-
*
|
|
9891
|
-
* ORDER IS LOAD-BEARING, for the same reason it is in `ILLUSTRATED_ROLES` above:
|
|
9892
|
-
* `partColumns` emits columns in role order and `magnitudeSub` (sdk/shelf.ts) takes the
|
|
9893
|
-
* first one, so it decides what `?<key>=…` filters and what a list column sorts by. `label`
|
|
9894
|
-
* first points both at the words a person wrote; `file` first would point them at a JSON
|
|
9895
|
-
* blob whose lexical order is an accident of how the entry's keys happen to be serialized.
|
|
9896
|
-
*
|
|
9897
|
-
* Both roles are PRIM LISTS, not predicates — unlike `illustrated`, which needs a predicate
|
|
9898
|
-
* because it must tell a picture from a PDF and every file kind stores the same prim. This
|
|
9899
|
-
* type accepts the whole file family, which is exactly what `prim: 'file'` names
|
|
9900
|
-
* (`registerFileType`, below in this file — the roles resolve lazily, long after it runs),
|
|
9901
|
-
* so a list says it precisely and a predicate would only restate it at more length.
|
|
9902
|
-
*/
|
|
9903
7972
|
var ATTACHMENT_ROLES = () => [role("label", ["text"], "string"), role("file", ["file"], "file")];
|
|
9904
7973
|
registerType("attachment", {
|
|
9905
7974
|
kind: "attachment",
|
|
@@ -9908,38 +7977,12 @@ registerType("attachment", {
|
|
|
9908
7977
|
roles: ATTACHMENT_ROLES,
|
|
9909
7978
|
roleZod: () => defaultRoleZod(ATTACHMENT_ROLES),
|
|
9910
7979
|
structureCode: "attachment_structure",
|
|
9911
|
-
|
|
7980
|
+
acceptsEmptyRow: true,
|
|
9912
7981
|
collectionView: "attachmentList"
|
|
9913
7982
|
});
|
|
9914
|
-
/**
|
|
9915
|
-
* An uploaded file and the label that names it as ONE value: `{ label, file }`, stored
|
|
9916
|
-
* across `<key>__label` / `<key>__file`. A file field alone can only ever show what the
|
|
9917
|
-
* uploader happened to call the file — `scan_20240817_final(2).pdf` — so the record page
|
|
9918
|
-
* states what a document IS beside it instead of leaving the reader to open every one.
|
|
9919
|
-
*
|
|
9920
|
-
* `multiple: true` is the ordinary modifier and takes the collection-group path, exactly as
|
|
9921
|
-
* `f.illustrated` and `f.period` do — a child table whose columns are the resolved parts.
|
|
9922
|
-
* The `file` role accepts any of the file family and defaults to `f.file` (accept `*`); a
|
|
9923
|
-
* shelf that wants the narrower document uploader fills it: `fields: { file: f.document({}) }`.
|
|
9924
|
-
*
|
|
9925
|
-
* @layer primitive
|
|
9926
|
-
* @prim attachment
|
|
9927
|
-
* @widget attachment
|
|
9928
|
-
* @example f.attachment({ label: 'mod.fields.attachment' })
|
|
9929
|
-
*/
|
|
9930
7983
|
function attachment(o) {
|
|
9931
7984
|
return declare("attachment", o);
|
|
9932
7985
|
}
|
|
9933
|
-
/**
|
|
9934
|
-
* 7 values, Mon–Sun. A thin wrapper over keyed({fixed}): a fixed collection,
|
|
9935
|
-
* keyed by the weekday list, one real value per day. Its own kind 'per-weekday'
|
|
9936
|
-
* dispatches a compact renderer. Storage: a grandchild table of {day, value} rows.
|
|
9937
|
-
*
|
|
9938
|
-
* @layer primitive
|
|
9939
|
-
* @prim —
|
|
9940
|
-
* @widget per-weekday
|
|
9941
|
-
* @example f.perWeekday({ label: 'mod.fields.hours' })
|
|
9942
|
-
*/
|
|
9943
7986
|
function perWeekday(o) {
|
|
9944
7987
|
return {
|
|
9945
7988
|
...keyed({
|
|
@@ -9962,45 +8005,18 @@ function perWeekday(o) {
|
|
|
9962
8005
|
kind: "per-weekday"
|
|
9963
8006
|
};
|
|
9964
8007
|
}
|
|
9965
|
-
/**
|
|
9966
|
-
* How a cross-field ordering check (a range's `from > to`, a period's) reads one endpoint
|
|
9967
|
-
* once `stripServerOwnedParts` has removed the slots the schema owns. The check must judge
|
|
9968
|
-
* the value the record will actually CARRY, never the client's echo of a server-owned slot:
|
|
9969
|
-
* that echo is discarded on the same write, so letting it decide would reject legitimate
|
|
9970
|
-
* writes over a value that never reaches storage, and would never see the value that does.
|
|
9971
|
-
*
|
|
9972
|
-
* - a CLIENT-owned (plain `stored`) endpoint → the parsed value, under its role name;
|
|
9973
|
-
* - a COMPUTED-AND-STORED endpoint → undefined, so the pair is not compared at all: the
|
|
9974
|
-
* server produces that value later in the write (a constant included — it is written by
|
|
9975
|
-
* the same pass as a function) and nothing here can know it ahead of time.
|
|
9976
|
-
*
|
|
9977
|
-
* With every role left to its default filling every endpoint is client-owned, and this is
|
|
9978
|
-
* exactly `row[role]` — the comparison, its code and its message are unchanged.
|
|
9979
|
-
*/
|
|
9980
8008
|
function endpointReader(parts, keys) {
|
|
9981
8009
|
return (role, stored) => stored?.[keys[role]];
|
|
9982
8010
|
}
|
|
9983
|
-
/** period's own granularity, defaulted like every other config-driven composite —
|
|
9984
|
-
* read the same way by `roles`/`roleZod`/`hints`. */
|
|
9985
8011
|
function periodGranularity(opts) {
|
|
9986
8012
|
return opts.config?.granularity ?? "day";
|
|
9987
8013
|
}
|
|
9988
|
-
/** The endpoint factory at a given granularity — a ported date/datetime FieldDecl. It
|
|
9989
|
-
* takes no key: an endpoint stores under its ROLE name (`from`/`until`), which
|
|
9990
|
-
* `resolveParts` supplies, so there was never a second name to pass here. */
|
|
9991
8014
|
function periodMkSubDecl(granularity) {
|
|
9992
8015
|
return (label) => granularity === "datetime" ? datetime({ label }) : date({
|
|
9993
8016
|
label,
|
|
9994
8017
|
rules: { granularity: granularity === "month" ? "month" : "day" }
|
|
9995
8018
|
});
|
|
9996
8019
|
}
|
|
9997
|
-
/**
|
|
9998
|
-
* One endpoint role of a `period`: its default depends on THIS field's own granularity
|
|
9999
|
-
* (`mkSubDecl`), so it writes its own `normalize` rather than using `role()`. A CORRECTED
|
|
10000
|
-
* filling still keeps the composite's own granularity (`canonical.opts`) — the same
|
|
10001
|
-
* `date`/`datetime` config the role's own default carries — with the author's
|
|
10002
|
-
* label/required/ui/value layered on top, exactly like `retypeTo`'s default.
|
|
10003
|
-
*/
|
|
10004
8020
|
function periodRole(key, label, mkSubDecl) {
|
|
10005
8021
|
const def = {
|
|
10006
8022
|
role: key,
|
|
@@ -10009,11 +8025,10 @@ function periodRole(key, label, mkSubDecl) {
|
|
|
10009
8025
|
if (d && accepts(def, d)) return d;
|
|
10010
8026
|
const canonical = mkSubDecl(label);
|
|
10011
8027
|
if (!d) return canonical;
|
|
10012
|
-
const { label: l,
|
|
8028
|
+
const { label: l, value } = d.opts;
|
|
10013
8029
|
return declare(canonical.factory, {
|
|
10014
8030
|
...canonical.opts,
|
|
10015
8031
|
label: l ?? canonical.opts.label,
|
|
10016
|
-
required,
|
|
10017
8032
|
value,
|
|
10018
8033
|
noSearch: d.opts.noSearch,
|
|
10019
8034
|
ui: uiOf(d.opts)
|
|
@@ -10022,15 +8037,10 @@ function periodRole(key, label, mkSubDecl) {
|
|
|
10022
8037
|
};
|
|
10023
8038
|
return def;
|
|
10024
8039
|
}
|
|
10025
|
-
/** Roles of a single `period`: the two date/datetime endpoints, at the type's granularity. */
|
|
10026
8040
|
function periodRoles(opts) {
|
|
10027
8041
|
const mkSubDecl = periodMkSubDecl(periodGranularity(opts));
|
|
10028
8042
|
return [periodRole("from", "core.period.from", mkSubDecl), periodRole("until", "core.period.until", mkSubDecl)];
|
|
10029
8043
|
}
|
|
10030
|
-
/** period's per-role default zod: each role unconditionally `.optional()`, at the type's
|
|
10031
|
-
* own granularity (`periodMkSubDecl`) — the schema a role falls back to when the author
|
|
10032
|
-
* did not override it (`partsRowShape`'s own rule: an overridden part is validated by its
|
|
10033
|
-
* OWN zod instead, never this default). */
|
|
10034
8044
|
function periodRoleZod(opts) {
|
|
10035
8045
|
const zod = materialize(periodMkSubDecl(periodGranularity(opts))("")).zod.optional();
|
|
10036
8046
|
return {
|
|
@@ -10038,9 +8048,6 @@ function periodRoleZod(opts) {
|
|
|
10038
8048
|
until: zod
|
|
10039
8049
|
};
|
|
10040
8050
|
}
|
|
10041
|
-
/** period's own ordering check: `from > until` is rejected — the same `endpointReader`
|
|
10042
|
-
* `rangeRefine` uses, comparing the ISO date/datetime strings directly (they sort
|
|
10043
|
-
* lexicographically) instead of `rangeRefine`'s numeric endpoints. */
|
|
10044
8051
|
function periodRefine(stored, parts) {
|
|
10045
8052
|
const endpoint = endpointReader(parts, partValueKeys(parts));
|
|
10046
8053
|
const f = endpoint("from", stored);
|
|
@@ -10065,39 +8072,20 @@ registerType("period", {
|
|
|
10065
8072
|
granularity: periodGranularity(opts)
|
|
10066
8073
|
})
|
|
10067
8074
|
});
|
|
10068
|
-
/**
|
|
10069
|
-
* Date/datetime period composite {from, until}; `multiple` → an array of periods stored
|
|
10070
|
-
* as a child table.
|
|
10071
|
-
*
|
|
10072
|
-
* @layer primitive
|
|
10073
|
-
* @prim period
|
|
10074
|
-
* @widget period
|
|
10075
|
-
* @example f.period({ label: 'mod.fields.employment' })
|
|
10076
|
-
*/
|
|
10077
8075
|
function period(raw) {
|
|
10078
8076
|
return declare("period", raw);
|
|
10079
8077
|
}
|
|
10080
|
-
/** Keys a file entry may carry. Everything else is rejected — see fileEntryIssue. */
|
|
10081
8078
|
var FILE_KEYS = /* @__PURE__ */ new Set([
|
|
10082
8079
|
"name",
|
|
10083
8080
|
"mime",
|
|
10084
8081
|
"size"
|
|
10085
8082
|
]);
|
|
10086
|
-
/** Keys that mean "the author pasted a remote address" — the one mistake worth naming. */
|
|
10087
8083
|
var FILE_URL_KEYS = [
|
|
10088
8084
|
"url",
|
|
10089
8085
|
"src",
|
|
10090
8086
|
"href",
|
|
10091
8087
|
"link"
|
|
10092
8088
|
];
|
|
10093
|
-
/**
|
|
10094
|
-
* A file entry points at a file already uploaded to the server: `{ name }`, where
|
|
10095
|
-
* name is the bare filename returned by POST /api/upload. Anything else — a remote
|
|
10096
|
-
* URL, an extra key, a path — is refused here, so a record can never hold a
|
|
10097
|
-
* reference the server cannot serve. `mime`/`size` are accepted (legacy payloads
|
|
10098
|
-
* and the web uploader send them) but the server overwrites them from disk.
|
|
10099
|
-
* Returns a vmsg code, or null when the entry is well-formed.
|
|
10100
|
-
*/
|
|
10101
8089
|
function fileEntryIssue(it) {
|
|
10102
8090
|
if (typeof it !== "object" || it === null || Array.isArray(it)) return "file_structure";
|
|
10103
8091
|
const rec = it;
|
|
@@ -10111,11 +8099,6 @@ function fileEntryIssue(it) {
|
|
|
10111
8099
|
if (rec["size"] !== void 0 && typeof rec["size"] !== "number") return "file_structure";
|
|
10112
8100
|
return null;
|
|
10113
8101
|
}
|
|
10114
|
-
/**
|
|
10115
|
-
* Shared by the whole file family's build(): a scalar {name,...} OR an array
|
|
10116
|
-
* [{name,...},...], native or JSON-string, stored as JSON regardless of `multiple` (the
|
|
10117
|
-
* renderer's gallery mode draws the single/many difference, not zod).
|
|
10118
|
-
*/
|
|
10119
8102
|
function fileZod() {
|
|
10120
8103
|
return unknown().superRefine((raw, ctx) => {
|
|
10121
8104
|
if (typeof raw === "string") try {
|
|
@@ -10141,10 +8124,6 @@ function fileZod() {
|
|
|
10141
8124
|
}
|
|
10142
8125
|
});
|
|
10143
8126
|
}
|
|
10144
|
-
/**
|
|
10145
|
-
* Registers one file-family TypeDef: same zod/column/json for all of them, `kind` (and
|
|
10146
|
-
* `widget`, always identical to `kind` in this family) is the only thing that varies.
|
|
10147
|
-
*/
|
|
10148
8127
|
function registerFileType(kind) {
|
|
10149
8128
|
registerType(kind, {
|
|
10150
8129
|
kind,
|
|
@@ -10162,123 +8141,41 @@ function registerFileType(kind) {
|
|
|
10162
8141
|
});
|
|
10163
8142
|
}
|
|
10164
8143
|
registerFileType("file");
|
|
10165
|
-
/**
|
|
10166
|
-
* Generic uploaded file reference `{ name }`, pointing at a file already on the server.
|
|
10167
|
-
*
|
|
10168
|
-
* @layer primitive
|
|
10169
|
-
* @prim file
|
|
10170
|
-
* @widget file
|
|
10171
|
-
* @example f.file({ label: 'mod.fields.attachment' })
|
|
10172
|
-
*/
|
|
10173
8144
|
function file(o) {
|
|
10174
8145
|
return declare("file", o);
|
|
10175
8146
|
}
|
|
10176
8147
|
registerFileType("document");
|
|
10177
|
-
/**
|
|
10178
|
-
* Uploaded document reference `{ name }`, rendered as a document widget.
|
|
10179
|
-
*
|
|
10180
|
-
* @layer primitive
|
|
10181
|
-
* @prim file
|
|
10182
|
-
* @widget document
|
|
10183
|
-
* @example f.document({ label: 'mod.fields.doc' })
|
|
10184
|
-
*/
|
|
10185
8148
|
function document(o) {
|
|
10186
8149
|
return declare("document", o);
|
|
10187
8150
|
}
|
|
10188
8151
|
registerFileType("audio");
|
|
10189
|
-
/**
|
|
10190
|
-
* Uploaded audio file reference `{ name }`, rendered as an audio player.
|
|
10191
|
-
*
|
|
10192
|
-
* @layer primitive
|
|
10193
|
-
* @prim file
|
|
10194
|
-
* @widget audio
|
|
10195
|
-
* @example f.audio({ label: 'mod.fields.recording' })
|
|
10196
|
-
*/
|
|
10197
8152
|
function audio(o) {
|
|
10198
8153
|
return declare("audio", o);
|
|
10199
8154
|
}
|
|
10200
8155
|
registerFileType("video");
|
|
10201
|
-
/**
|
|
10202
|
-
* Uploaded video file reference `{ name }`, rendered as a video player.
|
|
10203
|
-
*
|
|
10204
|
-
* @layer primitive
|
|
10205
|
-
* @prim file
|
|
10206
|
-
* @widget video
|
|
10207
|
-
* @example f.video({ label: 'mod.fields.clip' })
|
|
10208
|
-
*/
|
|
10209
8156
|
function video(o) {
|
|
10210
8157
|
return declare("video", o);
|
|
10211
8158
|
}
|
|
10212
8159
|
registerFileType("image");
|
|
10213
|
-
/**
|
|
10214
|
-
* Uploaded image reference `{ name }`, rendered as an inline image.
|
|
10215
|
-
*
|
|
10216
|
-
* @layer primitive
|
|
10217
|
-
* @prim file
|
|
10218
|
-
* @widget image
|
|
10219
|
-
* @example f.image({ label: 'mod.fields.photo' })
|
|
10220
|
-
*/
|
|
10221
8160
|
function image(o) {
|
|
10222
8161
|
return declare("image", o);
|
|
10223
8162
|
}
|
|
10224
8163
|
registerFileType("media");
|
|
10225
|
-
/**
|
|
10226
|
-
* Uploaded media reference `{ name }`, rendered as a generic media widget (mixed
|
|
10227
|
-
* image/video).
|
|
10228
|
-
*
|
|
10229
|
-
* @layer primitive
|
|
10230
|
-
* @prim file
|
|
10231
|
-
* @widget media
|
|
10232
|
-
* @example f.media({ label: 'mod.fields.attachment' })
|
|
10233
|
-
*/
|
|
10234
8164
|
function media(o) {
|
|
10235
8165
|
return declare("media", o);
|
|
10236
8166
|
}
|
|
10237
8167
|
registerFileType("avatar");
|
|
10238
|
-
/**
|
|
10239
|
-
* Uploaded image reference `{ name }`, rendered as a circular avatar.
|
|
10240
|
-
*
|
|
10241
|
-
* @layer primitive
|
|
10242
|
-
* @prim file
|
|
10243
|
-
* @widget avatar
|
|
10244
|
-
* @example f.avatar({ label: 'mod.fields.avatar' })
|
|
10245
|
-
*/
|
|
10246
8168
|
function avatar(o) {
|
|
10247
8169
|
return declare("avatar", o);
|
|
10248
8170
|
}
|
|
10249
8171
|
registerFileType("cover");
|
|
10250
|
-
/**
|
|
10251
|
-
* Uploaded image reference `{ name }`, rendered as a wide cover image.
|
|
10252
|
-
*
|
|
10253
|
-
* @layer primitive
|
|
10254
|
-
* @prim file
|
|
10255
|
-
* @widget cover
|
|
10256
|
-
* @example f.cover({ label: 'mod.fields.cover' })
|
|
10257
|
-
*/
|
|
10258
8172
|
function cover(o) {
|
|
10259
8173
|
return declare("cover", o);
|
|
10260
8174
|
}
|
|
10261
8175
|
registerFileType("poster");
|
|
10262
|
-
/**
|
|
10263
|
-
* Like `image` on the backend, but rendered large (portrait 2:3 poster).
|
|
10264
|
-
*
|
|
10265
|
-
* @layer primitive
|
|
10266
|
-
* @prim file
|
|
10267
|
-
* @widget poster
|
|
10268
|
-
* @example f.poster({ label: 'mod.fields.poster' })
|
|
10269
|
-
*/
|
|
10270
8176
|
function poster(o) {
|
|
10271
8177
|
return declare("poster", o);
|
|
10272
8178
|
}
|
|
10273
|
-
/**
|
|
10274
|
-
* Sugar over `keyed`: a collection table of {key, value} rows, `key` unique within a
|
|
10275
|
-
* record; the value cell's type is set via `fields.value` (default: plain string).
|
|
10276
|
-
*
|
|
10277
|
-
* @layer primitive
|
|
10278
|
-
* @prim —
|
|
10279
|
-
* @widget group
|
|
10280
|
-
* @example f.keyValue({ label: 'mod.fields.secrets' })
|
|
10281
|
-
*/
|
|
10282
8179
|
function keyValue(raw) {
|
|
10283
8180
|
const { fields, ...rest } = raw;
|
|
10284
8181
|
const o = normalizeOpts(rest);
|
|
@@ -10297,13 +8194,10 @@ function keyValue(raw) {
|
|
|
10297
8194
|
type: valueType
|
|
10298
8195
|
}
|
|
10299
8196
|
},
|
|
10300
|
-
by: "key"
|
|
10301
|
-
required: o.required
|
|
8197
|
+
by: "key"
|
|
10302
8198
|
});
|
|
10303
8199
|
}
|
|
10304
|
-
/** Roles of a range: the two endpoints, integer or real depending on `isInt`. */
|
|
10305
8200
|
var RANGE_ROLES = (isInt) => [role("from", ["number"], isInt ? "int" : "real"), role("to", ["number"], isInt ? "int" : "real")];
|
|
10306
|
-
/** Shared by numberRange/realRange's `roleZod`: the endpoint schema, int or real per `isInt`. */
|
|
10307
8201
|
function rangeEndpointZod(isInt, opts) {
|
|
10308
8202
|
const cfg = opts.config ?? {};
|
|
10309
8203
|
let n = number$1().finite();
|
|
@@ -10312,11 +8206,6 @@ function rangeEndpointZod(isInt, opts) {
|
|
|
10312
8206
|
if (cfg.max != null) n = n.max(cfg.max);
|
|
10313
8207
|
return n;
|
|
10314
8208
|
}
|
|
10315
|
-
/** Shared by numberRange/realRange's `refine`: `from > to` is rejected. Strip FIRST, then
|
|
10316
|
-
* compare — the check must judge the value the record will actually carry, never the
|
|
10317
|
-
* client's echo of an endpoint the SCHEMA owns (buildComposite strips it before calling
|
|
10318
|
-
* this). Each endpoint is read under the key it occupies in the value object — its role
|
|
10319
|
-
* name, always, now. */
|
|
10320
8209
|
function rangeRefine(stored, parts) {
|
|
10321
8210
|
const endpoint = endpointReader(parts, partValueKeys(parts));
|
|
10322
8211
|
const fromV = endpoint("from", stored);
|
|
@@ -10348,25 +8237,9 @@ function registerRangeType(kind, isInt) {
|
|
|
10348
8237
|
}
|
|
10349
8238
|
registerRangeType("numberRange", true);
|
|
10350
8239
|
registerRangeType("realRange", false);
|
|
10351
|
-
/**
|
|
10352
|
-
* Integer range composite {from, to}; `from > to` is rejected.
|
|
10353
|
-
*
|
|
10354
|
-
* @layer primitive
|
|
10355
|
-
* @prim numberRange
|
|
10356
|
-
* @widget numberRange
|
|
10357
|
-
* @example f.numberRange({ label: 'mod.fields.ageRange' })
|
|
10358
|
-
*/
|
|
10359
8240
|
function numberRange(o) {
|
|
10360
8241
|
return declare("numberRange", o);
|
|
10361
8242
|
}
|
|
10362
|
-
/**
|
|
10363
|
-
* Real-number range composite {from, to}; `from > to` is rejected.
|
|
10364
|
-
*
|
|
10365
|
-
* @layer primitive
|
|
10366
|
-
* @prim realRange
|
|
10367
|
-
* @widget realRange
|
|
10368
|
-
* @example f.realRange({ label: 'mod.fields.priceRange' })
|
|
10369
|
-
*/
|
|
10370
8243
|
function realRange(o) {
|
|
10371
8244
|
return declare("realRange", o);
|
|
10372
8245
|
}
|
|
@@ -10401,23 +8274,9 @@ registerType("embed", {
|
|
|
10401
8274
|
};
|
|
10402
8275
|
}
|
|
10403
8276
|
});
|
|
10404
|
-
/**
|
|
10405
|
-
* External content snapshot by URL — oEmbed/OpenGraph metadata (title, thumbnail,
|
|
10406
|
-
* provider, …) captured at save time via /api/embed.
|
|
10407
|
-
*
|
|
10408
|
-
* @layer primitive
|
|
10409
|
-
* @prim embed
|
|
10410
|
-
* @widget embed
|
|
10411
|
-
* @example f.embed({ label: 'mod.fields.link' })
|
|
10412
|
-
*/
|
|
10413
8277
|
function embed(o) {
|
|
10414
8278
|
return declare("embed", o);
|
|
10415
8279
|
}
|
|
10416
|
-
/**
|
|
10417
|
-
* Base primitives (storage-aligned) + structural types. Presets (thin wrappers
|
|
10418
|
-
* over these primitives) are added below from field-presets.ts and CANNOT override
|
|
10419
|
-
* any key from here (guard in composeF).
|
|
10420
|
-
*/
|
|
10421
8280
|
var PRIMITIVES = {
|
|
10422
8281
|
string,
|
|
10423
8282
|
text,
|
|
@@ -10467,7 +8326,6 @@ var PRIMITIVES = {
|
|
|
10467
8326
|
button,
|
|
10468
8327
|
backrefs
|
|
10469
8328
|
};
|
|
10470
|
-
/** Assembles `f`, guaranteeing no preset/block shadows a primitive. */
|
|
10471
8329
|
function composeF(presets, blocks) {
|
|
10472
8330
|
for (const k of Object.keys(presets)) if (k in PRIMITIVES) throw new Error(`[f] preset '${k}' overrides a primitive`);
|
|
10473
8331
|
for (const k of Object.keys(blocks)) if (k in PRIMITIVES) throw new Error(`[f] block '${k}' overrides a primitive`);
|
|
@@ -10485,12 +8343,11 @@ var field = new Proxy({}, {
|
|
|
10485
8343
|
has: (_t, k) => k in composedField()
|
|
10486
8344
|
});
|
|
10487
8345
|
function toClient(field) {
|
|
10488
|
-
const { kind, label, agent,
|
|
8346
|
+
const { kind, label, agent, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
|
|
10489
8347
|
return {
|
|
10490
8348
|
kind,
|
|
10491
8349
|
label,
|
|
10492
8350
|
...agent !== void 0 && { agent },
|
|
10493
|
-
required,
|
|
10494
8351
|
prim,
|
|
10495
8352
|
hints,
|
|
10496
8353
|
options,
|
|
@@ -10509,688 +8366,12 @@ function toClient(field) {
|
|
|
10509
8366
|
};
|
|
10510
8367
|
}
|
|
10511
8368
|
//#endregion
|
|
10512
|
-
//#region ../sdk/
|
|
8369
|
+
//#region ../sdk/dist/settings.js
|
|
10513
8370
|
function defineSettings(s) {
|
|
10514
8371
|
return materializeDef(s);
|
|
10515
8372
|
}
|
|
10516
8373
|
//#endregion
|
|
10517
|
-
//#region
|
|
10518
|
-
/******************************************************************************
|
|
10519
|
-
Copyright (c) Microsoft Corporation.
|
|
10520
|
-
|
|
10521
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
10522
|
-
purpose with or without fee is hereby granted.
|
|
10523
|
-
|
|
10524
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10525
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
10526
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
10527
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
10528
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
10529
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
10530
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
10531
|
-
***************************************************************************** */
|
|
10532
|
-
var extendStatics = function(d, b) {
|
|
10533
|
-
extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d, b) {
|
|
10534
|
-
d.__proto__ = b;
|
|
10535
|
-
} || function(d, b) {
|
|
10536
|
-
for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p];
|
|
10537
|
-
};
|
|
10538
|
-
return extendStatics(d, b);
|
|
10539
|
-
};
|
|
10540
|
-
function __extends(d, b) {
|
|
10541
|
-
if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
10542
|
-
extendStatics(d, b);
|
|
10543
|
-
function __() {
|
|
10544
|
-
this.constructor = d;
|
|
10545
|
-
}
|
|
10546
|
-
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
10547
|
-
}
|
|
10548
|
-
var typeChecker = function(type) {
|
|
10549
|
-
var typeString = "[object " + type + "]";
|
|
10550
|
-
return function(value) {
|
|
10551
|
-
return getClassName(value) === typeString;
|
|
10552
|
-
};
|
|
10553
|
-
};
|
|
10554
|
-
var getClassName = function(value) {
|
|
10555
|
-
return Object.prototype.toString.call(value);
|
|
10556
|
-
};
|
|
10557
|
-
var comparable = function(value) {
|
|
10558
|
-
if (value instanceof Date) return value.getTime();
|
|
10559
|
-
else if (isArray(value)) return value.map(comparable);
|
|
10560
|
-
else if (value && typeof value.toJSON === "function") return value.toJSON();
|
|
10561
|
-
return value;
|
|
10562
|
-
};
|
|
10563
|
-
var coercePotentiallyNull = function(value) {
|
|
10564
|
-
return value == null ? null : value;
|
|
10565
|
-
};
|
|
10566
|
-
var isArray = typeChecker("Array");
|
|
10567
|
-
var isObject = typeChecker("Object");
|
|
10568
|
-
var isFunction = typeChecker("Function");
|
|
10569
|
-
var isProperty = function(item, key) {
|
|
10570
|
-
return item.hasOwnProperty(key) && !isFunction(item[key]);
|
|
10571
|
-
};
|
|
10572
|
-
var isVanillaObject = function(value) {
|
|
10573
|
-
return value && (value.constructor === Object || value.constructor === Array || value.constructor.toString() === "function Object() { [native code] }" || value.constructor.toString() === "function Array() { [native code] }") && !value.toJSON;
|
|
10574
|
-
};
|
|
10575
|
-
var equals = function(a, b) {
|
|
10576
|
-
if (a == null && a == b) return true;
|
|
10577
|
-
if (a === b) return true;
|
|
10578
|
-
if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)) return false;
|
|
10579
|
-
if (isArray(a)) {
|
|
10580
|
-
if (a.length !== b.length) return false;
|
|
10581
|
-
for (var i = 0, length_1 = a.length; i < length_1; i++) if (!equals(a[i], b[i])) return false;
|
|
10582
|
-
return true;
|
|
10583
|
-
} else if (isObject(a)) {
|
|
10584
|
-
if (Object.keys(a).length !== Object.keys(b).length) return false;
|
|
10585
|
-
for (var key in a) if (!equals(a[key], b[key])) return false;
|
|
10586
|
-
return true;
|
|
10587
|
-
}
|
|
10588
|
-
return false;
|
|
10589
|
-
};
|
|
10590
|
-
/**
|
|
10591
|
-
* Walks through each value given the context - used for nested operations. E.g:
|
|
10592
|
-
* { "person.address": { $eq: "blarg" }}
|
|
10593
|
-
*/
|
|
10594
|
-
var walkKeyPathValues = function(item, keyPath, next, depth, key, owner) {
|
|
10595
|
-
var currentKey = keyPath[depth];
|
|
10596
|
-
if (isArray(item) && isNaN(Number(currentKey)) && !isProperty(item, currentKey)) {
|
|
10597
|
-
for (var i = 0, length_1 = item.length; i < length_1; i++) if (!walkKeyPathValues(item[i], keyPath, next, depth, i, item)) return false;
|
|
10598
|
-
}
|
|
10599
|
-
if (depth === keyPath.length || item == null) return next(item, key, owner, depth === 0, depth === keyPath.length);
|
|
10600
|
-
return walkKeyPathValues(item[currentKey], keyPath, next, depth + 1, currentKey, item);
|
|
10601
|
-
};
|
|
10602
|
-
var BaseOperation = function() {
|
|
10603
|
-
function BaseOperation(params, owneryQuery, options, name) {
|
|
10604
|
-
this.params = params;
|
|
10605
|
-
this.owneryQuery = owneryQuery;
|
|
10606
|
-
this.options = options;
|
|
10607
|
-
this.name = name;
|
|
10608
|
-
this.init();
|
|
10609
|
-
}
|
|
10610
|
-
BaseOperation.prototype.init = function() {};
|
|
10611
|
-
BaseOperation.prototype.reset = function() {
|
|
10612
|
-
this.done = false;
|
|
10613
|
-
this.keep = false;
|
|
10614
|
-
};
|
|
10615
|
-
return BaseOperation;
|
|
10616
|
-
}();
|
|
10617
|
-
var GroupOperation = function(_super) {
|
|
10618
|
-
__extends(GroupOperation, _super);
|
|
10619
|
-
function GroupOperation(params, owneryQuery, options, children) {
|
|
10620
|
-
var _this = _super.call(this, params, owneryQuery, options) || this;
|
|
10621
|
-
_this.children = children;
|
|
10622
|
-
return _this;
|
|
10623
|
-
}
|
|
10624
|
-
/**
|
|
10625
|
-
*/
|
|
10626
|
-
GroupOperation.prototype.reset = function() {
|
|
10627
|
-
this.keep = false;
|
|
10628
|
-
this.done = false;
|
|
10629
|
-
for (var i = 0, length_2 = this.children.length; i < length_2; i++) this.children[i].reset();
|
|
10630
|
-
};
|
|
10631
|
-
/**
|
|
10632
|
-
*/
|
|
10633
|
-
GroupOperation.prototype.childrenNext = function(item, key, owner, root, leaf) {
|
|
10634
|
-
var done = true;
|
|
10635
|
-
var keep = true;
|
|
10636
|
-
for (var i = 0, length_3 = this.children.length; i < length_3; i++) {
|
|
10637
|
-
var childOperation = this.children[i];
|
|
10638
|
-
if (!childOperation.done) childOperation.next(item, key, owner, root, leaf);
|
|
10639
|
-
if (!childOperation.keep) keep = false;
|
|
10640
|
-
if (childOperation.done) {
|
|
10641
|
-
if (!childOperation.keep) break;
|
|
10642
|
-
} else done = false;
|
|
10643
|
-
}
|
|
10644
|
-
this.done = done;
|
|
10645
|
-
this.keep = keep;
|
|
10646
|
-
};
|
|
10647
|
-
return GroupOperation;
|
|
10648
|
-
}(BaseOperation);
|
|
10649
|
-
var NamedGroupOperation = function(_super) {
|
|
10650
|
-
__extends(NamedGroupOperation, _super);
|
|
10651
|
-
function NamedGroupOperation(params, owneryQuery, options, children, name) {
|
|
10652
|
-
var _this = _super.call(this, params, owneryQuery, options, children) || this;
|
|
10653
|
-
_this.name = name;
|
|
10654
|
-
return _this;
|
|
10655
|
-
}
|
|
10656
|
-
return NamedGroupOperation;
|
|
10657
|
-
}(GroupOperation);
|
|
10658
|
-
var QueryOperation = function(_super) {
|
|
10659
|
-
__extends(QueryOperation, _super);
|
|
10660
|
-
function QueryOperation() {
|
|
10661
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10662
|
-
_this.propop = true;
|
|
10663
|
-
return _this;
|
|
10664
|
-
}
|
|
10665
|
-
/**
|
|
10666
|
-
*/
|
|
10667
|
-
QueryOperation.prototype.next = function(item, key, parent, root) {
|
|
10668
|
-
this.childrenNext(item, key, parent, root);
|
|
10669
|
-
};
|
|
10670
|
-
return QueryOperation;
|
|
10671
|
-
}(GroupOperation);
|
|
10672
|
-
var NestedOperation = function(_super) {
|
|
10673
|
-
__extends(NestedOperation, _super);
|
|
10674
|
-
function NestedOperation(keyPath, params, owneryQuery, options, children) {
|
|
10675
|
-
var _this = _super.call(this, params, owneryQuery, options, children) || this;
|
|
10676
|
-
_this.keyPath = keyPath;
|
|
10677
|
-
_this.propop = true;
|
|
10678
|
-
/**
|
|
10679
|
-
*/
|
|
10680
|
-
_this._nextNestedValue = function(value, key, owner, root, leaf) {
|
|
10681
|
-
_this.childrenNext(value, key, owner, root, leaf);
|
|
10682
|
-
return !_this.done;
|
|
10683
|
-
};
|
|
10684
|
-
return _this;
|
|
10685
|
-
}
|
|
10686
|
-
/**
|
|
10687
|
-
*/
|
|
10688
|
-
NestedOperation.prototype.next = function(item, key, parent) {
|
|
10689
|
-
walkKeyPathValues(item, this.keyPath, this._nextNestedValue, 0, key, parent);
|
|
10690
|
-
};
|
|
10691
|
-
return NestedOperation;
|
|
10692
|
-
}(GroupOperation);
|
|
10693
|
-
var createTester = function(a, compare) {
|
|
10694
|
-
if (a instanceof Function) return a;
|
|
10695
|
-
if (a instanceof RegExp) return function(b) {
|
|
10696
|
-
var result = typeof b === "string" && a.test(b);
|
|
10697
|
-
a.lastIndex = 0;
|
|
10698
|
-
return result;
|
|
10699
|
-
};
|
|
10700
|
-
var comparableA = comparable(a);
|
|
10701
|
-
return function(b) {
|
|
10702
|
-
return compare(comparableA, comparable(b));
|
|
10703
|
-
};
|
|
10704
|
-
};
|
|
10705
|
-
var EqualsOperation = function(_super) {
|
|
10706
|
-
__extends(EqualsOperation, _super);
|
|
10707
|
-
function EqualsOperation() {
|
|
10708
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10709
|
-
_this.propop = true;
|
|
10710
|
-
return _this;
|
|
10711
|
-
}
|
|
10712
|
-
EqualsOperation.prototype.init = function() {
|
|
10713
|
-
this._test = createTester(this.params, this.options.compare);
|
|
10714
|
-
};
|
|
10715
|
-
EqualsOperation.prototype.next = function(item, key, parent) {
|
|
10716
|
-
if (!Array.isArray(parent) || parent.hasOwnProperty(key)) {
|
|
10717
|
-
if (this._test(item, key, parent)) {
|
|
10718
|
-
this.done = true;
|
|
10719
|
-
this.keep = true;
|
|
10720
|
-
}
|
|
10721
|
-
}
|
|
10722
|
-
};
|
|
10723
|
-
return EqualsOperation;
|
|
10724
|
-
}(BaseOperation);
|
|
10725
|
-
var numericalOperationCreator = function(createNumericalOperation) {
|
|
10726
|
-
return function(params, owneryQuery, options, name) {
|
|
10727
|
-
return createNumericalOperation(params, owneryQuery, options, name);
|
|
10728
|
-
};
|
|
10729
|
-
};
|
|
10730
|
-
var numericalOperation = function(createTester) {
|
|
10731
|
-
return numericalOperationCreator(function(params, owneryQuery, options, name) {
|
|
10732
|
-
var typeofParams = typeof comparable(params);
|
|
10733
|
-
var test = createTester(params);
|
|
10734
|
-
return new EqualsOperation(function(b) {
|
|
10735
|
-
var actualValue = coercePotentiallyNull(b);
|
|
10736
|
-
return typeof comparable(actualValue) === typeofParams && test(actualValue);
|
|
10737
|
-
}, owneryQuery, options, name);
|
|
10738
|
-
});
|
|
10739
|
-
};
|
|
10740
|
-
var createNamedOperation = function(name, params, parentQuery, options) {
|
|
10741
|
-
var operationCreator = options.operations[name];
|
|
10742
|
-
if (!operationCreator) throwUnsupportedOperation(name);
|
|
10743
|
-
return operationCreator(params, parentQuery, options, name);
|
|
10744
|
-
};
|
|
10745
|
-
var throwUnsupportedOperation = function(name) {
|
|
10746
|
-
throw new Error("Unsupported operation: ".concat(name));
|
|
10747
|
-
};
|
|
10748
|
-
var containsOperation = function(query, options) {
|
|
10749
|
-
for (var key in query) if (options.operations.hasOwnProperty(key) || key.charAt(0) === "$") return true;
|
|
10750
|
-
return false;
|
|
10751
|
-
};
|
|
10752
|
-
var createNestedOperation = function(keyPath, nestedQuery, parentKey, owneryQuery, options) {
|
|
10753
|
-
if (containsOperation(nestedQuery, options)) {
|
|
10754
|
-
var _a = createQueryOperations(nestedQuery, parentKey, options), selfOperations = _a[0];
|
|
10755
|
-
if (_a[1].length) throw new Error("Property queries must contain only operations, or exact objects.");
|
|
10756
|
-
return new NestedOperation(keyPath, nestedQuery, owneryQuery, options, selfOperations);
|
|
10757
|
-
}
|
|
10758
|
-
return new NestedOperation(keyPath, nestedQuery, owneryQuery, options, [new EqualsOperation(nestedQuery, owneryQuery, options)]);
|
|
10759
|
-
};
|
|
10760
|
-
var createQueryOperation = function(query, owneryQuery, _a) {
|
|
10761
|
-
if (owneryQuery === void 0) owneryQuery = null;
|
|
10762
|
-
var _b = _a === void 0 ? {} : _a, compare = _b.compare, operations = _b.operations;
|
|
10763
|
-
var options = {
|
|
10764
|
-
compare: compare || equals,
|
|
10765
|
-
operations: Object.assign({}, operations || {})
|
|
10766
|
-
};
|
|
10767
|
-
var _c = createQueryOperations(query, null, options), selfOperations = _c[0], nestedOperations = _c[1];
|
|
10768
|
-
var ops = [];
|
|
10769
|
-
if (selfOperations.length) ops.push(new NestedOperation([], query, owneryQuery, options, selfOperations));
|
|
10770
|
-
ops.push.apply(ops, nestedOperations);
|
|
10771
|
-
if (ops.length === 1) return ops[0];
|
|
10772
|
-
return new QueryOperation(query, owneryQuery, options, ops);
|
|
10773
|
-
};
|
|
10774
|
-
var createQueryOperations = function(query, parentKey, options) {
|
|
10775
|
-
var selfOperations = [];
|
|
10776
|
-
var nestedOperations = [];
|
|
10777
|
-
if (!isVanillaObject(query)) {
|
|
10778
|
-
selfOperations.push(new EqualsOperation(query, query, options));
|
|
10779
|
-
return [selfOperations, nestedOperations];
|
|
10780
|
-
}
|
|
10781
|
-
for (var key in query) if (options.operations.hasOwnProperty(key)) {
|
|
10782
|
-
var op = createNamedOperation(key, query[key], query, options);
|
|
10783
|
-
if (op) {
|
|
10784
|
-
if (!op.propop && parentKey && !options.operations[parentKey]) throw new Error("Malformed query. ".concat(key, " cannot be matched against property."));
|
|
10785
|
-
}
|
|
10786
|
-
if (op != null) selfOperations.push(op);
|
|
10787
|
-
} else if (key.charAt(0) === "$") throwUnsupportedOperation(key);
|
|
10788
|
-
else nestedOperations.push(createNestedOperation(key.split("."), query[key], key, query, options));
|
|
10789
|
-
return [selfOperations, nestedOperations];
|
|
10790
|
-
};
|
|
10791
|
-
var createOperationTester = function(operation) {
|
|
10792
|
-
return function(item, key, owner) {
|
|
10793
|
-
operation.reset();
|
|
10794
|
-
operation.next(item, key, owner);
|
|
10795
|
-
return operation.keep;
|
|
10796
|
-
};
|
|
10797
|
-
};
|
|
10798
|
-
var $Ne = function(_super) {
|
|
10799
|
-
__extends($Ne, _super);
|
|
10800
|
-
function $Ne() {
|
|
10801
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10802
|
-
_this.propop = true;
|
|
10803
|
-
return _this;
|
|
10804
|
-
}
|
|
10805
|
-
$Ne.prototype.init = function() {
|
|
10806
|
-
this._test = createTester(this.params, this.options.compare);
|
|
10807
|
-
};
|
|
10808
|
-
$Ne.prototype.reset = function() {
|
|
10809
|
-
_super.prototype.reset.call(this);
|
|
10810
|
-
this.keep = true;
|
|
10811
|
-
};
|
|
10812
|
-
$Ne.prototype.next = function(item) {
|
|
10813
|
-
if (this._test(item)) {
|
|
10814
|
-
this.done = true;
|
|
10815
|
-
this.keep = false;
|
|
10816
|
-
}
|
|
10817
|
-
};
|
|
10818
|
-
return $Ne;
|
|
10819
|
-
}(BaseOperation);
|
|
10820
|
-
var $ElemMatch = function(_super) {
|
|
10821
|
-
__extends($ElemMatch, _super);
|
|
10822
|
-
function $ElemMatch() {
|
|
10823
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10824
|
-
_this.propop = true;
|
|
10825
|
-
return _this;
|
|
10826
|
-
}
|
|
10827
|
-
$ElemMatch.prototype.init = function() {
|
|
10828
|
-
if (!this.params || typeof this.params !== "object") throw new Error("Malformed query. $elemMatch must by an object.");
|
|
10829
|
-
this._queryOperation = createQueryOperation(this.params, this.owneryQuery, this.options);
|
|
10830
|
-
};
|
|
10831
|
-
$ElemMatch.prototype.reset = function() {
|
|
10832
|
-
_super.prototype.reset.call(this);
|
|
10833
|
-
this._queryOperation.reset();
|
|
10834
|
-
};
|
|
10835
|
-
$ElemMatch.prototype.next = function(item) {
|
|
10836
|
-
if (isArray(item)) {
|
|
10837
|
-
for (var i = 0, length_1 = item.length; i < length_1; i++) {
|
|
10838
|
-
this._queryOperation.reset();
|
|
10839
|
-
var child = item[i];
|
|
10840
|
-
this._queryOperation.next(child, i, item, false);
|
|
10841
|
-
this.keep = this.keep || this._queryOperation.keep;
|
|
10842
|
-
}
|
|
10843
|
-
this.done = true;
|
|
10844
|
-
} else {
|
|
10845
|
-
this.done = false;
|
|
10846
|
-
this.keep = false;
|
|
10847
|
-
}
|
|
10848
|
-
};
|
|
10849
|
-
return $ElemMatch;
|
|
10850
|
-
}(BaseOperation);
|
|
10851
|
-
var $Not = function(_super) {
|
|
10852
|
-
__extends($Not, _super);
|
|
10853
|
-
function $Not() {
|
|
10854
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10855
|
-
_this.propop = true;
|
|
10856
|
-
return _this;
|
|
10857
|
-
}
|
|
10858
|
-
$Not.prototype.init = function() {
|
|
10859
|
-
this._queryOperation = createQueryOperation(this.params, this.owneryQuery, this.options);
|
|
10860
|
-
};
|
|
10861
|
-
$Not.prototype.reset = function() {
|
|
10862
|
-
_super.prototype.reset.call(this);
|
|
10863
|
-
this._queryOperation.reset();
|
|
10864
|
-
};
|
|
10865
|
-
$Not.prototype.next = function(item, key, owner, root) {
|
|
10866
|
-
this._queryOperation.next(item, key, owner, root);
|
|
10867
|
-
this.done = this._queryOperation.done;
|
|
10868
|
-
this.keep = !this._queryOperation.keep;
|
|
10869
|
-
};
|
|
10870
|
-
return $Not;
|
|
10871
|
-
}(BaseOperation);
|
|
10872
|
-
var $Size = function(_super) {
|
|
10873
|
-
__extends($Size, _super);
|
|
10874
|
-
function $Size() {
|
|
10875
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10876
|
-
_this.propop = true;
|
|
10877
|
-
return _this;
|
|
10878
|
-
}
|
|
10879
|
-
$Size.prototype.init = function() {};
|
|
10880
|
-
$Size.prototype.next = function(item) {
|
|
10881
|
-
if (isArray(item) && item.length === this.params) {
|
|
10882
|
-
this.done = true;
|
|
10883
|
-
this.keep = true;
|
|
10884
|
-
}
|
|
10885
|
-
};
|
|
10886
|
-
return $Size;
|
|
10887
|
-
}(BaseOperation);
|
|
10888
|
-
var assertGroupNotEmpty = function(values) {
|
|
10889
|
-
if (values.length === 0) throw new Error("$and/$or/$nor must be a nonempty array");
|
|
10890
|
-
};
|
|
10891
|
-
var $Or = function(_super) {
|
|
10892
|
-
__extends($Or, _super);
|
|
10893
|
-
function $Or() {
|
|
10894
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10895
|
-
_this.propop = false;
|
|
10896
|
-
return _this;
|
|
10897
|
-
}
|
|
10898
|
-
$Or.prototype.init = function() {
|
|
10899
|
-
var _this = this;
|
|
10900
|
-
assertGroupNotEmpty(this.params);
|
|
10901
|
-
this._ops = this.params.map(function(op) {
|
|
10902
|
-
return createQueryOperation(op, null, _this.options);
|
|
10903
|
-
});
|
|
10904
|
-
};
|
|
10905
|
-
$Or.prototype.reset = function() {
|
|
10906
|
-
this.done = false;
|
|
10907
|
-
this.keep = false;
|
|
10908
|
-
for (var i = 0, length_2 = this._ops.length; i < length_2; i++) this._ops[i].reset();
|
|
10909
|
-
};
|
|
10910
|
-
$Or.prototype.next = function(item, key, owner) {
|
|
10911
|
-
var done = false;
|
|
10912
|
-
var success = false;
|
|
10913
|
-
for (var i = 0, length_3 = this._ops.length; i < length_3; i++) {
|
|
10914
|
-
var op = this._ops[i];
|
|
10915
|
-
op.next(item, key, owner);
|
|
10916
|
-
if (op.keep) {
|
|
10917
|
-
done = true;
|
|
10918
|
-
success = op.keep;
|
|
10919
|
-
break;
|
|
10920
|
-
}
|
|
10921
|
-
}
|
|
10922
|
-
this.keep = success;
|
|
10923
|
-
this.done = done;
|
|
10924
|
-
};
|
|
10925
|
-
return $Or;
|
|
10926
|
-
}(BaseOperation);
|
|
10927
|
-
var $Nor = function(_super) {
|
|
10928
|
-
__extends($Nor, _super);
|
|
10929
|
-
function $Nor() {
|
|
10930
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10931
|
-
_this.propop = false;
|
|
10932
|
-
return _this;
|
|
10933
|
-
}
|
|
10934
|
-
$Nor.prototype.next = function(item, key, owner) {
|
|
10935
|
-
_super.prototype.next.call(this, item, key, owner);
|
|
10936
|
-
this.keep = !this.keep;
|
|
10937
|
-
};
|
|
10938
|
-
return $Nor;
|
|
10939
|
-
}($Or);
|
|
10940
|
-
var $In = function(_super) {
|
|
10941
|
-
__extends($In, _super);
|
|
10942
|
-
function $In() {
|
|
10943
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
10944
|
-
_this.propop = true;
|
|
10945
|
-
return _this;
|
|
10946
|
-
}
|
|
10947
|
-
$In.prototype.init = function() {
|
|
10948
|
-
var _this = this;
|
|
10949
|
-
var params = Array.isArray(this.params) ? this.params : [this.params];
|
|
10950
|
-
this._testers = params.map(function(value) {
|
|
10951
|
-
if (containsOperation(value, _this.options)) throw new Error("cannot nest $ under ".concat(_this.name.toLowerCase()));
|
|
10952
|
-
return createTester(value, _this.options.compare);
|
|
10953
|
-
});
|
|
10954
|
-
};
|
|
10955
|
-
$In.prototype.next = function(item, key, owner) {
|
|
10956
|
-
var done = false;
|
|
10957
|
-
var success = false;
|
|
10958
|
-
for (var i = 0, length_4 = this._testers.length; i < length_4; i++) {
|
|
10959
|
-
var test = this._testers[i];
|
|
10960
|
-
if (test(item)) {
|
|
10961
|
-
done = true;
|
|
10962
|
-
success = true;
|
|
10963
|
-
break;
|
|
10964
|
-
}
|
|
10965
|
-
}
|
|
10966
|
-
this.keep = success;
|
|
10967
|
-
this.done = done;
|
|
10968
|
-
};
|
|
10969
|
-
return $In;
|
|
10970
|
-
}(BaseOperation);
|
|
10971
|
-
var $Nin = function(_super) {
|
|
10972
|
-
__extends($Nin, _super);
|
|
10973
|
-
function $Nin(params, ownerQuery, options, name) {
|
|
10974
|
-
var _this = _super.call(this, params, ownerQuery, options, name) || this;
|
|
10975
|
-
_this.propop = true;
|
|
10976
|
-
_this._in = new $In(params, ownerQuery, options, name);
|
|
10977
|
-
return _this;
|
|
10978
|
-
}
|
|
10979
|
-
$Nin.prototype.next = function(item, key, owner, root) {
|
|
10980
|
-
this._in.next(item, key, owner);
|
|
10981
|
-
if (isArray(owner) && !root) {
|
|
10982
|
-
if (this._in.keep) {
|
|
10983
|
-
this.keep = false;
|
|
10984
|
-
this.done = true;
|
|
10985
|
-
} else if (key == owner.length - 1) {
|
|
10986
|
-
this.keep = true;
|
|
10987
|
-
this.done = true;
|
|
10988
|
-
}
|
|
10989
|
-
} else {
|
|
10990
|
-
this.keep = !this._in.keep;
|
|
10991
|
-
this.done = true;
|
|
10992
|
-
}
|
|
10993
|
-
};
|
|
10994
|
-
$Nin.prototype.reset = function() {
|
|
10995
|
-
_super.prototype.reset.call(this);
|
|
10996
|
-
this._in.reset();
|
|
10997
|
-
};
|
|
10998
|
-
return $Nin;
|
|
10999
|
-
}(BaseOperation);
|
|
11000
|
-
var $Exists = function(_super) {
|
|
11001
|
-
__extends($Exists, _super);
|
|
11002
|
-
function $Exists() {
|
|
11003
|
-
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
11004
|
-
_this.propop = true;
|
|
11005
|
-
return _this;
|
|
11006
|
-
}
|
|
11007
|
-
$Exists.prototype.next = function(item, key, owner, root, leaf) {
|
|
11008
|
-
if (!leaf) {
|
|
11009
|
-
this.done = true;
|
|
11010
|
-
this.keep = !this.params;
|
|
11011
|
-
} else if (owner.hasOwnProperty(key) === this.params) {
|
|
11012
|
-
this.done = true;
|
|
11013
|
-
this.keep = true;
|
|
11014
|
-
}
|
|
11015
|
-
};
|
|
11016
|
-
return $Exists;
|
|
11017
|
-
}(BaseOperation);
|
|
11018
|
-
var $And = function(_super) {
|
|
11019
|
-
__extends($And, _super);
|
|
11020
|
-
function $And(params, owneryQuery, options, name) {
|
|
11021
|
-
var _this = _super.call(this, params, owneryQuery, options, params.map(function(query) {
|
|
11022
|
-
return createQueryOperation(query, owneryQuery, options);
|
|
11023
|
-
}), name) || this;
|
|
11024
|
-
_this.propop = false;
|
|
11025
|
-
assertGroupNotEmpty(params);
|
|
11026
|
-
return _this;
|
|
11027
|
-
}
|
|
11028
|
-
$And.prototype.next = function(item, key, owner, root) {
|
|
11029
|
-
this.childrenNext(item, key, owner, root);
|
|
11030
|
-
};
|
|
11031
|
-
return $And;
|
|
11032
|
-
}(NamedGroupOperation);
|
|
11033
|
-
var $All = function(_super) {
|
|
11034
|
-
__extends($All, _super);
|
|
11035
|
-
function $All(params, owneryQuery, options, name) {
|
|
11036
|
-
var _this = _super.call(this, params, owneryQuery, options, params.map(function(query) {
|
|
11037
|
-
return createQueryOperation(query, owneryQuery, options);
|
|
11038
|
-
}), name) || this;
|
|
11039
|
-
_this.propop = true;
|
|
11040
|
-
return _this;
|
|
11041
|
-
}
|
|
11042
|
-
$All.prototype.next = function(item, key, owner, root) {
|
|
11043
|
-
this.childrenNext(item, key, owner, root);
|
|
11044
|
-
};
|
|
11045
|
-
return $All;
|
|
11046
|
-
}(NamedGroupOperation);
|
|
11047
|
-
var $eq = function(params, owneryQuery, options) {
|
|
11048
|
-
return new EqualsOperation(params, owneryQuery, options);
|
|
11049
|
-
};
|
|
11050
|
-
var $ne = function(params, owneryQuery, options, name) {
|
|
11051
|
-
return new $Ne(params, owneryQuery, options, name);
|
|
11052
|
-
};
|
|
11053
|
-
var $or = function(params, owneryQuery, options, name) {
|
|
11054
|
-
return new $Or(params, owneryQuery, options, name);
|
|
11055
|
-
};
|
|
11056
|
-
var $nor = function(params, owneryQuery, options, name) {
|
|
11057
|
-
return new $Nor(params, owneryQuery, options, name);
|
|
11058
|
-
};
|
|
11059
|
-
var $elemMatch = function(params, owneryQuery, options, name) {
|
|
11060
|
-
return new $ElemMatch(params, owneryQuery, options, name);
|
|
11061
|
-
};
|
|
11062
|
-
var $nin = function(params, owneryQuery, options, name) {
|
|
11063
|
-
return new $Nin(params, owneryQuery, options, name);
|
|
11064
|
-
};
|
|
11065
|
-
var $in = function(params, owneryQuery, options, name) {
|
|
11066
|
-
return new $In(params, owneryQuery, options, name);
|
|
11067
|
-
};
|
|
11068
|
-
var $lt = numericalOperation(function(params) {
|
|
11069
|
-
return function(b) {
|
|
11070
|
-
return b != null && b < params;
|
|
11071
|
-
};
|
|
11072
|
-
});
|
|
11073
|
-
var $lte = numericalOperation(function(params) {
|
|
11074
|
-
return function(b) {
|
|
11075
|
-
return b === params || b <= params;
|
|
11076
|
-
};
|
|
11077
|
-
});
|
|
11078
|
-
var $gt = numericalOperation(function(params) {
|
|
11079
|
-
return function(b) {
|
|
11080
|
-
return b != null && b > params;
|
|
11081
|
-
};
|
|
11082
|
-
});
|
|
11083
|
-
var $gte = numericalOperation(function(params) {
|
|
11084
|
-
return function(b) {
|
|
11085
|
-
return b === params || b >= params;
|
|
11086
|
-
};
|
|
11087
|
-
});
|
|
11088
|
-
var $mod = function(_a, owneryQuery, options) {
|
|
11089
|
-
var mod = _a[0], equalsValue = _a[1];
|
|
11090
|
-
return new EqualsOperation(function(b) {
|
|
11091
|
-
return comparable(b) % mod === equalsValue;
|
|
11092
|
-
}, owneryQuery, options);
|
|
11093
|
-
};
|
|
11094
|
-
var $exists = function(params, owneryQuery, options, name) {
|
|
11095
|
-
return new $Exists(params, owneryQuery, options, name);
|
|
11096
|
-
};
|
|
11097
|
-
var $regex = function(pattern, owneryQuery, options) {
|
|
11098
|
-
return new EqualsOperation(new RegExp(pattern, owneryQuery.$options), owneryQuery, options);
|
|
11099
|
-
};
|
|
11100
|
-
var $not = function(params, owneryQuery, options, name) {
|
|
11101
|
-
return new $Not(params, owneryQuery, options, name);
|
|
11102
|
-
};
|
|
11103
|
-
var typeAliases = {
|
|
11104
|
-
number: function(v) {
|
|
11105
|
-
return typeof v === "number";
|
|
11106
|
-
},
|
|
11107
|
-
string: function(v) {
|
|
11108
|
-
return typeof v === "string";
|
|
11109
|
-
},
|
|
11110
|
-
bool: function(v) {
|
|
11111
|
-
return typeof v === "boolean";
|
|
11112
|
-
},
|
|
11113
|
-
array: function(v) {
|
|
11114
|
-
return Array.isArray(v);
|
|
11115
|
-
},
|
|
11116
|
-
null: function(v) {
|
|
11117
|
-
return v === null;
|
|
11118
|
-
},
|
|
11119
|
-
timestamp: function(v) {
|
|
11120
|
-
return v instanceof Date;
|
|
11121
|
-
}
|
|
11122
|
-
};
|
|
11123
|
-
var $type = function(clazz, owneryQuery, options) {
|
|
11124
|
-
return new EqualsOperation(function(b) {
|
|
11125
|
-
if (typeof clazz === "string") {
|
|
11126
|
-
if (!typeAliases[clazz]) throw new Error("Type alias does not exist");
|
|
11127
|
-
return typeAliases[clazz](b);
|
|
11128
|
-
}
|
|
11129
|
-
return b != null ? b instanceof clazz || b.constructor === clazz : false;
|
|
11130
|
-
}, owneryQuery, options);
|
|
11131
|
-
};
|
|
11132
|
-
var $and = function(params, ownerQuery, options, name) {
|
|
11133
|
-
return new $And(params, ownerQuery, options, name);
|
|
11134
|
-
};
|
|
11135
|
-
var $all = function(params, ownerQuery, options, name) {
|
|
11136
|
-
return new $All(params, ownerQuery, options, name);
|
|
11137
|
-
};
|
|
11138
|
-
var $size = function(params, ownerQuery, options) {
|
|
11139
|
-
return new $Size(params, ownerQuery, options, "$size");
|
|
11140
|
-
};
|
|
11141
|
-
var $options = function() {
|
|
11142
|
-
return null;
|
|
11143
|
-
};
|
|
11144
|
-
var $where = function(params, ownerQuery, options) {
|
|
11145
|
-
var test;
|
|
11146
|
-
if (isFunction(params)) test = params;
|
|
11147
|
-
else if (!process.env.CSP_ENABLED) test = new Function("obj", "return " + params);
|
|
11148
|
-
else throw new Error("In CSP mode, sift does not support strings in \"$where\" condition");
|
|
11149
|
-
return new EqualsOperation(function(b) {
|
|
11150
|
-
return test.bind(b)(b);
|
|
11151
|
-
}, ownerQuery, options);
|
|
11152
|
-
};
|
|
11153
|
-
var defaultOperations = /*#__PURE__*/ Object.freeze({
|
|
11154
|
-
__proto__: null,
|
|
11155
|
-
$Size,
|
|
11156
|
-
$all,
|
|
11157
|
-
$and,
|
|
11158
|
-
$elemMatch,
|
|
11159
|
-
$eq,
|
|
11160
|
-
$exists,
|
|
11161
|
-
$gt,
|
|
11162
|
-
$gte,
|
|
11163
|
-
$in,
|
|
11164
|
-
$lt,
|
|
11165
|
-
$lte,
|
|
11166
|
-
$mod,
|
|
11167
|
-
$ne,
|
|
11168
|
-
$nin,
|
|
11169
|
-
$nor,
|
|
11170
|
-
$not,
|
|
11171
|
-
$options,
|
|
11172
|
-
$or,
|
|
11173
|
-
$regex,
|
|
11174
|
-
$size,
|
|
11175
|
-
$type,
|
|
11176
|
-
$where
|
|
11177
|
-
});
|
|
11178
|
-
var createDefaultQueryOperation = function(query, ownerQuery, _a) {
|
|
11179
|
-
var _b = _a === void 0 ? {} : _a, compare = _b.compare, operations = _b.operations;
|
|
11180
|
-
return createQueryOperation(query, ownerQuery, {
|
|
11181
|
-
compare,
|
|
11182
|
-
operations: Object.assign({}, defaultOperations, operations || {})
|
|
11183
|
-
});
|
|
11184
|
-
};
|
|
11185
|
-
var createDefaultQueryTester = function(query, options) {
|
|
11186
|
-
if (options === void 0) options = {};
|
|
11187
|
-
return createOperationTester(createDefaultQueryOperation(query, null, options));
|
|
11188
|
-
};
|
|
11189
|
-
createDefaultQueryTester.createEqualsOperation;
|
|
11190
|
-
//#endregion
|
|
11191
|
-
//#region ../sdk/src/shelf.ts
|
|
11192
|
-
/** Flat [storageKey, FieldMeta] pairs of the parent table's columns.
|
|
11193
|
-
* layout group → flattened; embedded group → prefix `key__`; collection → skipped. */
|
|
8374
|
+
//#region ../sdk/dist/shelf.js
|
|
11194
8375
|
function fieldEntries(items) {
|
|
11195
8376
|
const out = [];
|
|
11196
8377
|
for (const raw of items) {
|
|
@@ -11204,78 +8385,14 @@ function fieldEntries(items) {
|
|
|
11204
8385
|
}
|
|
11205
8386
|
return out;
|
|
11206
8387
|
}
|
|
11207
|
-
/**
|
|
11208
|
-
* Rejects a keyless computed element (`field.x({ value: fn })`, no `key`) anywhere in
|
|
11209
|
-
* the tree: at the shelf's own top level, inside a keyless layout group (`f.group`/
|
|
11210
|
-
* `f.row`/`f.sheet` without a storage key), AND inside a keyed embedded group or
|
|
11211
|
-
* collection row. A keyless computed node never carries a key of its own regardless of what it is
|
|
11212
|
-
* nested in, so the enclosing group having a storage key does not give it one — there
|
|
11213
|
-
* is still no mechanism that computes and returns such an element: `applyStoredParts`
|
|
11214
|
-
* (`@coffer-org/server/part-injection`) only ever writes into a KEYED unit — a
|
|
11215
|
-
* top-level field with `fm.compute`, or a composite part, and every composite part now
|
|
11216
|
-
* always has a key too (its role name) — so a bare keyless value node has no column to
|
|
11217
|
-
* land in either way. Left unrejected, a keyless computed element nested inside a
|
|
11218
|
-
* keyed group would silently render blank forever instead of failing loudly at
|
|
11219
|
-
* `defineShelf` time — recurse into every group, not only keyless ones.
|
|
11220
|
-
*/
|
|
11221
8388
|
function assertNoKeylessCompute(items, owner) {
|
|
11222
8389
|
for (const it of items) {
|
|
11223
8390
|
if ("value" in it && typeof it.value === "function") throw new Error(`${owner}: computed element needs a key — the server computes it and needs a name to return it under`);
|
|
11224
8391
|
if (hasChildren(it)) assertNoKeylessCompute(it.fields, owner);
|
|
11225
8392
|
}
|
|
11226
8393
|
}
|
|
11227
|
-
/**
|
|
11228
|
-
* Warns (does not throw) about the ONE composite shape mixed ownership is still wrong for: a
|
|
11229
|
-
* MANDATED client-owned stored part beside a server-owned one — one computed-and-stored (the
|
|
11230
|
-
* server writes that column) next to a plain stored one (the client writes that one) that the
|
|
11231
|
-
* declaration demands a value for.
|
|
11232
|
-
*
|
|
11233
|
-
* Mixed ownership itself is supported: the write path MATERIALIZES such a composite and
|
|
11234
|
-
* computes the server's part even when the write does not carry the field, leaving the
|
|
11235
|
-
* client's columns as they were (THE ABSENT-COMPOSITE RULE in
|
|
11236
|
-
* `@coffer-org/server/part-injection`). What a MANDATE adds is a demand the materialized value
|
|
11237
|
-
* cannot honour: the half-value the server produces on its own — the computed column filled,
|
|
11238
|
-
* the client's empty — reads back as `{value: 5, unit: null}` and is rejected by the
|
|
11239
|
-
* composite's own zod, yet it is non-null enough to satisfy the required check on the next
|
|
11240
|
-
* PATCH. The write path therefore refuses to materialize that one combination, and this names
|
|
11241
|
-
* it rather than leaving the author to wonder why the computed part never fills.
|
|
11242
|
-
*
|
|
11243
|
-
* The mandate is read through `mandatedClientParts`, which counts the FIELD's `required` AND
|
|
11244
|
-
* each OVERRIDDEN FILLING's own — `partsRowShape` honours both, so a check that read only the
|
|
11245
|
-
* field let `fields: { unit: f.string({ required: true }) }` through silently.
|
|
11246
|
-
*
|
|
11247
|
-
* Recurses into every group — an embedded group's composite and a collection row's carry
|
|
11248
|
-
* exactly the same mandate as a top-level one.
|
|
11249
|
-
*/
|
|
11250
|
-
function warnMixedOwnedParts(items, owner, path = "") {
|
|
11251
|
-
for (const it of items) {
|
|
11252
|
-
if (hasChildren(it)) {
|
|
11253
|
-
warnMixedOwnedParts(it.fields, owner, isStorageGroup(it) ? `${path}${it.key}.` : path);
|
|
11254
|
-
continue;
|
|
11255
|
-
}
|
|
11256
|
-
if (!(hasValue(it) && it.key !== void 0) || !it.type.parts) continue;
|
|
11257
|
-
const mixed = mixedOwnedStoredParts(it.type.parts);
|
|
11258
|
-
if (!mixed) continue;
|
|
11259
|
-
const mandated = mandatedClientParts(it.type.required, it.type.parts);
|
|
11260
|
-
if (!mandated.length) continue;
|
|
11261
|
-
console.warn(`${owner}: composite '${path}${it.key}' mandates a client-owned stored part (${mandated.join(", ")}) and mixes it with a server-owned stored part (${mixed.server.join(", ")}) — a mandated part is the client's to declare, so the write path does NOT materialize this composite and the computed part stays absent until a client sends the field. Either drop \`required\` (from the field or from the ${mandated.join("/")} filling), or give every stored part a \`value\` (the composite becomes fully server-owned).`);
|
|
11262
|
-
}
|
|
11263
|
-
}
|
|
11264
|
-
/**
|
|
11265
|
-
* The declaration-time checks that are about a FIELD LIST, not about a shelf: they read
|
|
11266
|
-
* `fields` and nothing else, so they are equally true of an extend's fields — an extend
|
|
11267
|
-
* stores composites in its own table through the very same write path (`upsertExtendRecord`
|
|
11268
|
-
* → `applyStoredParts`) and renders them through the same layout walk.
|
|
11269
|
-
*
|
|
11270
|
-
* They live in ONE function called by both `defineShelf` and `defineExtend` rather than
|
|
11271
|
-
* being copied: `defineExtend` had NEITHER, so a keyless computed element on an extend
|
|
11272
|
-
* rendered blank forever instead of throwing, and a mixed-ownership composite on an extend
|
|
11273
|
-
* warned about nothing at all. `owner` is the prefix each diagnostic is tagged with
|
|
11274
|
-
* (`[shelf] things/item`, `[extend] device_info`), the one thing the two callers differ in.
|
|
11275
|
-
*/
|
|
11276
8394
|
function checkFieldDeclaration(fields, owner) {
|
|
11277
8395
|
assertNoKeylessCompute(fields, owner);
|
|
11278
|
-
warnMixedOwnedParts(fields, owner);
|
|
11279
8396
|
}
|
|
11280
8397
|
function defineShelf(m) {
|
|
11281
8398
|
const built = materializeDef(m);
|
|
@@ -11293,32 +8410,12 @@ function defineShelf(m) {
|
|
|
11293
8410
|
checkFieldDeclaration(built.fields, `[shelf] ${built.library}/${built.shelf}`);
|
|
11294
8411
|
return built;
|
|
11295
8412
|
}
|
|
11296
|
-
/**
|
|
11297
|
-
* Contract of a real entity:
|
|
11298
|
-
* id — uuid (the ONLY required system field, auto).
|
|
11299
|
-
* The rest (including name) are regular fields in ShelfDef.fields.
|
|
11300
|
-
* created_at/updated_at — system infrastructure (outside fields, auto-managed).
|
|
11301
|
-
*/
|
|
11302
|
-
/**
|
|
11303
|
-
* Keyed fields of the record itself, in declaration order: top level and plain
|
|
11304
|
-
* layout groups. An embedded group or a collection describes a sub-record, so its
|
|
11305
|
-
* fields are not candidates for the record's own heading.
|
|
11306
|
-
*/
|
|
11307
8413
|
function ownFieldEntries(items) {
|
|
11308
8414
|
const out = [];
|
|
11309
8415
|
for (const it of items) if (isNamedField(it)) out.push([it.key, it.type]);
|
|
11310
8416
|
else if (hasChildren(it) && !isCollectionGroup(it) && !isEmbeddedGroup(it)) out.push(...ownFieldEntries(it.fields));
|
|
11311
8417
|
return out;
|
|
11312
8418
|
}
|
|
11313
|
-
/**
|
|
11314
|
-
* Key of the record's title field (lists, relation pickers, inlines, record heading).
|
|
11315
|
-
* Priority: views.title → the first keyed kind:'title' field → 'id'.
|
|
11316
|
-
*
|
|
11317
|
-
* The title is declared, never guessed: a shelf that wants a heading marks the field
|
|
11318
|
-
* with field.title(), or names another field through views.title when the heading is
|
|
11319
|
-
* not a text field (a date, a computed value). A shelf that declares nothing resolves
|
|
11320
|
-
* to 'id' and renders no heading — every field stays in the body with its own label.
|
|
11321
|
-
*/
|
|
11322
8419
|
function titleKey(m) {
|
|
11323
8420
|
if (m.views?.title) return m.views.title;
|
|
11324
8421
|
const declared = ownFieldEntries(m.fields).find(([, f]) => f.kind === "title");
|
|
@@ -11344,29 +8441,32 @@ var bus_route_default = defineShelf({
|
|
|
11344
8441
|
] },
|
|
11345
8442
|
fields: {
|
|
11346
8443
|
name: field.title({ label: "core.fields.name" }),
|
|
8444
|
+
/**
|
|
8445
|
+
* What this route IS, read before anything else: its number, its two ends and the
|
|
8446
|
+
* stop these timetables are measured at. They already drew as one row — the spans
|
|
8447
|
+
* said so — but below the description and with their labels above. A stats cell
|
|
8448
|
+
* inverts that and makes them the record's headline, which is what they are.
|
|
8449
|
+
*/
|
|
8450
|
+
route: field.stats({ fields: {
|
|
8451
|
+
route_number: field.int({ label: "transit.bus_route.fields.route_number" }),
|
|
8452
|
+
from: field.string({
|
|
8453
|
+
label: "transit.bus_route.fields.from",
|
|
8454
|
+
rules: { max: 100 }
|
|
8455
|
+
}),
|
|
8456
|
+
to: field.string({
|
|
8457
|
+
label: "transit.bus_route.fields.to",
|
|
8458
|
+
rules: { max: 100 }
|
|
8459
|
+
}),
|
|
8460
|
+
stop: field.string({
|
|
8461
|
+
label: "transit.bus_route.fields.stop",
|
|
8462
|
+
rules: { max: 200 }
|
|
8463
|
+
})
|
|
8464
|
+
} }),
|
|
8465
|
+
hairline: field.divider({}),
|
|
11347
8466
|
description: field.text({
|
|
11348
8467
|
label: "transit.bus_route.fields.description",
|
|
11349
8468
|
rules: { max: 1e3 }
|
|
11350
8469
|
}),
|
|
11351
|
-
route_number: field.int({
|
|
11352
|
-
label: "transit.bus_route.fields.route_number",
|
|
11353
|
-
ui: { span: 3 }
|
|
11354
|
-
}),
|
|
11355
|
-
from: field.string({
|
|
11356
|
-
label: "transit.bus_route.fields.from",
|
|
11357
|
-
rules: { max: 100 },
|
|
11358
|
-
ui: { span: 3 }
|
|
11359
|
-
}),
|
|
11360
|
-
to: field.string({
|
|
11361
|
-
label: "transit.bus_route.fields.to",
|
|
11362
|
-
rules: { max: 100 },
|
|
11363
|
-
ui: { span: 3 }
|
|
11364
|
-
}),
|
|
11365
|
-
stop: field.string({
|
|
11366
|
-
label: "transit.bus_route.fields.stop",
|
|
11367
|
-
rules: { max: 200 },
|
|
11368
|
-
ui: { span: 3 }
|
|
11369
|
-
}),
|
|
11370
8470
|
weekday_times: field.timetable({
|
|
11371
8471
|
label: "transit.bus_route.fields.weekday_times",
|
|
11372
8472
|
ui: { span: 12 }
|
|
@@ -11392,6 +8492,8 @@ var src_default = definePlugin({
|
|
|
11392
8492
|
id: "transit",
|
|
11393
8493
|
version: "1.0.0",
|
|
11394
8494
|
dependsOn: [],
|
|
8495
|
+
label: "transit.plugin.label",
|
|
8496
|
+
description: "transit.plugin.description",
|
|
11395
8497
|
libraries: [{
|
|
11396
8498
|
meta: defineLibrary({
|
|
11397
8499
|
id: "transit",
|