@drhomet/mithaq 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/LICENSES/payload.txt +22 -0
- package/LICENSES/ponytail.txt +21 -0
- package/dist/asas/actor.d.ts +96 -0
- package/dist/asas/actor.js +114 -0
- package/dist/asas/build.d.ts +231 -0
- package/dist/asas/build.js +787 -0
- package/dist/asas/cli/mithaq.d.ts +2 -0
- package/dist/asas/cli/mithaq.js +145 -0
- package/dist/asas/cli/upgrade.d.ts +20 -0
- package/dist/asas/cli/upgrade.js +147 -0
- package/dist/asas/http/fetch.d.ts +23 -0
- package/dist/asas/http/fetch.js +115 -0
- package/dist/asas/http/orpc.d.ts +43 -0
- package/dist/asas/http/orpc.js +135 -0
- package/dist/asas/runner/bulk.d.ts +20 -0
- package/dist/asas/runner/bulk.js +191 -0
- package/dist/asas/runner/errors.d.ts +60 -0
- package/dist/asas/runner/errors.js +59 -0
- package/dist/asas/runner/read.d.ts +68 -0
- package/dist/asas/runner/read.js +579 -0
- package/dist/asas/runner/runtime.d.ts +21 -0
- package/dist/asas/runner/runtime.js +8 -0
- package/dist/asas/runner/steps.d.ts +400 -0
- package/dist/asas/runner/steps.js +271 -0
- package/dist/asas/runner/tenant.d.ts +54 -0
- package/dist/asas/runner/tenant.js +213 -0
- package/dist/asas/runner/write.d.ts +444 -0
- package/dist/asas/runner/write.js +791 -0
- package/dist/auth/better-auth/access.d.ts +15 -0
- package/dist/auth/better-auth/access.js +71 -0
- package/dist/auth/better-auth/generate.d.ts +29 -0
- package/dist/auth/better-auth/generate.js +310 -0
- package/dist/auth/better-auth/index.d.ts +92 -0
- package/dist/auth/better-auth/index.js +81 -0
- package/dist/auth/better-auth/tenant.d.ts +18 -0
- package/dist/auth/better-auth/tenant.js +26 -0
- package/dist/auth/local/index.d.ts +285 -0
- package/dist/auth/local/index.js +736 -0
- package/dist/base/decimal.d.ts +9 -0
- package/dist/base/decimal.js +16 -0
- package/dist/base/fields.d.ts +123 -0
- package/dist/base/fields.js +51 -0
- package/dist/base/index.d.ts +46 -0
- package/dist/base/index.js +5 -0
- package/dist/base/kinds.d.ts +54 -0
- package/dist/base/kinds.js +78 -0
- package/dist/base/rules.d.ts +73 -0
- package/dist/base/rules.js +80 -0
- package/dist/drizzle/index.d.ts +438 -0
- package/dist/drizzle/index.js +832 -0
- package/dist/drizzle/kinds.d.ts +27 -0
- package/dist/drizzle/kinds.js +41 -0
- package/dist/drizzle/migrate.d.ts +133 -0
- package/dist/drizzle/migrate.js +1285 -0
- package/dist/drizzle/proxy/index.d.ts +1 -0
- package/dist/drizzle/proxy/index.js +1 -0
- package/dist/drizzle/proxy/node-postgres.d.ts +1 -0
- package/dist/drizzle/proxy/node-postgres.js +1 -0
- package/dist/drizzle/proxy/pg-core.d.ts +1 -0
- package/dist/drizzle/proxy/pg-core.js +1 -0
- package/dist/drizzle/rules.d.ts +336 -0
- package/dist/drizzle/rules.js +44 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +36 -0
- package/dist/mithaq/define.d.ts +288 -0
- package/dist/mithaq/define.js +43 -0
- package/dist/mithaq/generate-messages.d.ts +38 -0
- package/dist/mithaq/generate-messages.js +234 -0
- package/dist/mithaq/language.d.ts +335 -0
- package/dist/mithaq/language.js +76 -0
- package/dist/mithaq/lock.d.ts +71 -0
- package/dist/mithaq/lock.js +329 -0
- package/dist/mithaq/messages.d.ts +55 -0
- package/dist/mithaq/messages.js +106 -0
- package/dist/mithaq/refusal.d.ts +6 -0
- package/dist/mithaq/refusal.js +6 -0
- package/dist/mithaq/resolve.d.ts +10 -0
- package/dist/mithaq/resolve.js +954 -0
- package/dist/mithaq/rows.d.ts +87 -0
- package/dist/mithaq/rows.js +174 -0
- package/dist/mithaq/rukn.d.ts +72 -0
- package/dist/mithaq/rukn.js +15 -0
- package/dist/mithaq/standard-schema.d.ts +38 -0
- package/dist/mithaq/standard-schema.js +2 -0
- package/dist/mithaq/types.d.ts +358 -0
- package/dist/mithaq/types.js +15 -0
- package/dist/mithaq/user.d.ts +19 -0
- package/dist/mithaq/user.js +16 -0
- package/dist/mithaq/where.d.ts +32 -0
- package/dist/mithaq/where.js +87 -0
- package/dist/rbac/index.d.ts +105 -0
- package/dist/rbac/index.js +182 -0
- package/dist/rbac/steps.d.ts +22 -0
- package/dist/rbac/steps.js +161 -0
- package/dist/rich-text/index.d.ts +27 -0
- package/dist/rich-text/index.js +58 -0
- package/dist/rich-text/rich-text.d.ts +159 -0
- package/dist/rich-text/rich-text.js +372 -0
- package/dist/screen/client.d.ts +153 -0
- package/dist/screen/client.js +68 -0
- package/dist/screen/generate.d.ts +14 -0
- package/dist/screen/generate.js +226 -0
- package/dist/screen/index.d.ts +479 -0
- package/dist/screen/index.js +645 -0
- package/dist/screen/react.d.ts +95 -0
- package/dist/screen/react.js +58 -0
- package/dist/screen/studio.d.ts +24 -0
- package/dist/screen/studio.js +68 -0
- package/dist/service/index.d.ts +63 -0
- package/dist/service/index.js +15 -0
- package/dist/storage/allowed-type.d.ts +5 -0
- package/dist/storage/allowed-type.js +7 -0
- package/dist/storage/index.d.ts +43 -0
- package/dist/storage/index.js +44 -0
- package/dist/storage/storage.d.ts +384 -0
- package/dist/storage/storage.js +405 -0
- package/dist/versions/index.d.ts +37 -0
- package/dist/versions/index.js +8 -0
- package/dist/versions/versions.d.ts +104 -0
- package/dist/versions/versions.js +160 -0
- package/dist/zod/index.d.ts +184 -0
- package/dist/zod/index.js +121 -0
- package/dist/zod/kinds.d.ts +34 -0
- package/dist/zod/kinds.js +49 -0
- package/dist/zod/rules.d.ts +117 -0
- package/dist/zod/rules.js +67 -0
- package/package.json +63 -4
- package/r/mithaq-cells.json +20 -0
- package/r/mithaq-controls.json +37 -0
- package/r/mithaq-directional-icon.json +15 -0
- package/r/mithaq-expected-updated-at.json +15 -0
- package/r/mithaq-field-shell.json +18 -0
- package/r/mithaq-filter-operators.json +15 -0
- package/r/mithaq-form.json +33 -0
- package/r/mithaq-list-columns.json +23 -0
- package/r/mithaq-list-filters.json +24 -0
- package/r/mithaq-list-pagination.json +24 -0
- package/r/mithaq-list-query.json +18 -0
- package/r/mithaq-list-selection.json +19 -0
- package/r/mithaq-list.json +33 -0
- package/r/mithaq-nav.json +24 -0
- package/r/mithaq-nodes.json +34 -0
- package/r/mithaq-relationship-cell.json +20 -0
- package/r/mithaq-relationship-control.json +20 -0
- package/r/mithaq-rich-text.json +40 -0
- package/r/mithaq-screens-host.json +96 -0
- package/r/mithaq-storage.json +44 -0
- package/r/mithaq-upload.json +29 -0
- package/r/registry.json +550 -0
- package/README.md +0 -3
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { same } from '../mithaq/where.js';
|
|
2
|
+
import { snakeCase, versionsTableOf } from '../mithaq/resolve.js';
|
|
3
|
+
import { providerOf } from '../base/rules.js';
|
|
4
|
+
import { missing } from '../asas/runner/errors.js';
|
|
5
|
+
import { findVersionByID, findVersions, issueRefusal, restoreVersion } from '../asas/runner/steps.js';
|
|
6
|
+
/**
|
|
7
|
+
* The `_status` field a kayan that keeps drafts spreads into its own `fields`.
|
|
8
|
+
* A plain read leaves out a row that is not `published`.
|
|
9
|
+
*/
|
|
10
|
+
export const draftFields = {
|
|
11
|
+
_status: {
|
|
12
|
+
kind: 'text',
|
|
13
|
+
label: { en: 'Status', ar: 'الحالة' },
|
|
14
|
+
options: [
|
|
15
|
+
{ value: 'draft', label: { en: 'Draft', ar: 'مسودة' } },
|
|
16
|
+
{ value: 'published', label: { en: 'Published', ar: 'منشور' } },
|
|
17
|
+
],
|
|
18
|
+
rules: { defaultValue: 'draft', index: true },
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The three operations a kayan that keeps versions spreads beside `defaultOperations`:
|
|
23
|
+
* `findVersions`, `findVersionByID` and `restoreVersion`.
|
|
24
|
+
*/
|
|
25
|
+
export const versionOperations = {
|
|
26
|
+
findVersions: { steps: [findVersions] },
|
|
27
|
+
findVersionByID: { steps: [findVersionByID] },
|
|
28
|
+
restoreVersion: { steps: [restoreVersion] },
|
|
29
|
+
};
|
|
30
|
+
export const versionsOf = ({ kayan, kept, name, window, }) => {
|
|
31
|
+
const where = `Kayan "${kayan.name}" writes "targets.${name}"`;
|
|
32
|
+
if (kayan.single)
|
|
33
|
+
throw new Error(`${where}, and a global keeps no versions: versions are a kayan's alone.`);
|
|
34
|
+
// A version is addressed by its own uuid, which the kayan's id shape takes only where its id is a uuid.
|
|
35
|
+
if (kayan.fields.id?.kind !== 'uuid') {
|
|
36
|
+
throw new Error(`${where}, and its id is of kind "${String(kayan.fields.id?.kind)}", while a version is addressed by a uuid. Keep the uuid id defaultFields spreads.`);
|
|
37
|
+
}
|
|
38
|
+
if (kept.drafts && !kayan.fields._status) {
|
|
39
|
+
throw new Error(`${where} with drafts, and declares no field "_status", which is where a row says whether it is a draft or published. Spread draftFields into its fields.`);
|
|
40
|
+
}
|
|
41
|
+
const max = kept.max ?? window;
|
|
42
|
+
if (!Number.isInteger(max) || max < 0)
|
|
43
|
+
throw new Error(`${where} with max ${String(max)}, and max is a whole number of versions, 0 keeping every one.`);
|
|
44
|
+
const interval = typeof kept.drafts === 'object' && typeof kept.drafts.autosave === 'object' ? kept.drafts.autosave.interval : 2000;
|
|
45
|
+
if (!Number.isInteger(interval) || interval <= 0)
|
|
46
|
+
throw new Error(`${where} with an autosave interval of ${String(interval)}, and an interval is a whole number of milliseconds above 0.`);
|
|
47
|
+
const table = versionsTableOf(kayan);
|
|
48
|
+
const column = (field, kind, named = snakeCase(field)) => ({ name: field, kind, column: named, kayan: table });
|
|
49
|
+
return {
|
|
50
|
+
table,
|
|
51
|
+
drafts: !!kept.drafts,
|
|
52
|
+
autosave: typeof kept.drafts === 'object' && kept.drafts.autosave ? { interval } : false,
|
|
53
|
+
max,
|
|
54
|
+
kayan: {
|
|
55
|
+
name: table,
|
|
56
|
+
table,
|
|
57
|
+
...(kayan.schemaName !== undefined && { schemaName: kayan.schemaName }),
|
|
58
|
+
store: kayan.store,
|
|
59
|
+
schema: kayan.schema,
|
|
60
|
+
operations: {},
|
|
61
|
+
indexes: [],
|
|
62
|
+
// No prototype, so a query naming `toString` finds no field rather than a function.
|
|
63
|
+
fields: Object.assign(Object.create(null), {
|
|
64
|
+
id: column('id', 'uuid'),
|
|
65
|
+
parent: { ...column('parent', kayan.fields.id.kind, 'parent_id'), target: kayan },
|
|
66
|
+
latest: column('latest', 'checkbox'),
|
|
67
|
+
autosave: column('autosave', 'checkbox'),
|
|
68
|
+
createdAt: column('createdAt', 'datetime'),
|
|
69
|
+
updatedAt: column('updatedAt', 'datetime'),
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const newest = { field: 'updatedAt', direction: 'desc' };
|
|
75
|
+
// jsonb holds a moment as its ISO string, so each datetime field becomes a `Date` again.
|
|
76
|
+
export const revived = (kayan, snapshot) => {
|
|
77
|
+
const row = { ...snapshot };
|
|
78
|
+
const moment = (held) => (typeof held === 'string' ? new Date(held) : held);
|
|
79
|
+
for (const field of Object.values(kayan.fields)) {
|
|
80
|
+
const held = row[field.name];
|
|
81
|
+
if (field.kind !== 'datetime' || held === null || held === undefined)
|
|
82
|
+
continue;
|
|
83
|
+
row[field.name] = typeof held === 'object' && !(held instanceof Date)
|
|
84
|
+
? Object.fromEntries(Object.entries(held).map(([locale, one]) => [locale, moment(one)]))
|
|
85
|
+
: moment(held);
|
|
86
|
+
}
|
|
87
|
+
return row;
|
|
88
|
+
};
|
|
89
|
+
export const latestVersion = async ({ kayan, store, parent, tx, }) => (await store.find({ kayan: kayan.versions.table, args: {
|
|
90
|
+
limit: 1,
|
|
91
|
+
page: 1,
|
|
92
|
+
sort: newest,
|
|
93
|
+
where: { and: [{ parent: { equals: parent } }, { latest: { equals: true } }] },
|
|
94
|
+
}, tx })).docs[0];
|
|
95
|
+
// In the write's own transaction, so the audit step after it covers it. An
|
|
96
|
+
// autosave overwrites the latest version only where that one is an autosave.
|
|
97
|
+
export const saveVersion = async ({ kayan, store, writes, tx, row, autosave, }) => {
|
|
98
|
+
const { table, max } = kayan.versions;
|
|
99
|
+
const latest = await latestVersion({ kayan, store, parent: row.id, tx });
|
|
100
|
+
// A row's version moments only rise: two saves in one millisecond would let the
|
|
101
|
+
// previous-version read skip one and leave the order to the random ids.
|
|
102
|
+
const now = new Date(Math.max(Date.now(), latest ? new Date(latest.updatedAt).getTime() + 1 : 0));
|
|
103
|
+
if (autosave && latest?.autosave === true) {
|
|
104
|
+
await writes.update({ tx, kayan: table, id: latest.id, data: { version: row, updatedAt: now } });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (latest)
|
|
108
|
+
await writes.update({ tx, kayan: table, id: latest.id, data: { latest: false } });
|
|
109
|
+
await writes.insert({ tx, kayan: table, data: { parent: row.id, version: row, latest: true, autosave, createdAt: now, updatedAt: now } });
|
|
110
|
+
if (max < 1)
|
|
111
|
+
return;
|
|
112
|
+
// Never the one just inserted: two saves in one write can share a millisecond.
|
|
113
|
+
const older = { and: [{ parent: { equals: row.id } }, { latest: { equals: false } }] };
|
|
114
|
+
const past = await store.find({ kayan: table, args: { ...(max > 1 ? { limit: max - 1, page: 2 } : { limit: 0, page: 1 }), sort: newest, where: older }, tx });
|
|
115
|
+
for (const old of past.docs)
|
|
116
|
+
await writes.remove({ tx, kayan: table, id: old.id });
|
|
117
|
+
};
|
|
118
|
+
// `_status` is left out because the write decides it: the row always holds the
|
|
119
|
+
// last published copy, so a restore that writes the row publishes it.
|
|
120
|
+
export const changedColumns = ({ kayan, snapshot, row, whole = false, }) => Object.fromEntries(Object.values(kayan.fields)
|
|
121
|
+
.filter((field) => field.name !== 'id' && field.name !== '_status' && field.kind !== 'join' &&
|
|
122
|
+
providerOf(field, 'update') === 'client' && field.name in snapshot &&
|
|
123
|
+
((whole && snapshot[field.name] != null && !same(snapshot[field.name], [])) || !same(snapshot[field.name], row[field.name])))
|
|
124
|
+
.map((field) => [field.name, snapshot[field.name] ?? null]));
|
|
125
|
+
// The row's read access comes first, so nothing about a row the actor cannot
|
|
126
|
+
// read is told: such a version is refused in the words an absent one is.
|
|
127
|
+
export const restoreInput = async ({ kayan, store, data, actor, authorize, admin, alive, seesDrafts, admits, }) => {
|
|
128
|
+
const absent = () => missing({
|
|
129
|
+
message: `No "${kayan.name}" version with id ${String(data.id)} to restore.`,
|
|
130
|
+
key: 'error.not_found.row',
|
|
131
|
+
params: { kayan: kayan.name, id: data.id, operation: 'restoreVersion' },
|
|
132
|
+
});
|
|
133
|
+
// Nothing has parsed a version id yet, and it is the versions table's own uuid whatever the row's id is.
|
|
134
|
+
if (typeof data.id !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(data.id)) {
|
|
135
|
+
throw issueRefusal([{ message: 'Invalid UUID', path: ['id'] }], 'mithaq');
|
|
136
|
+
}
|
|
137
|
+
// A restore may publish a draft nothing else shows this actor, so it asks the
|
|
138
|
+
// key a draft read asks; without it the id answers as absent.
|
|
139
|
+
if (!seesDrafts)
|
|
140
|
+
throw absent();
|
|
141
|
+
const [version] = (await store.find({ kayan: kayan.versions.table, args: { limit: 1, page: 1, sort: newest, where: { id: { equals: data.id } } } })).docs;
|
|
142
|
+
// No version has that id, so it may name a published row whose pending draft is
|
|
143
|
+
// being discarded: restoring the row onto itself publishes the last published copy.
|
|
144
|
+
const parent = version ? version.parent : data.id;
|
|
145
|
+
// Asked with no operation, so no key but the restore's own is asked for.
|
|
146
|
+
const rowOf = async (id, admitted) => {
|
|
147
|
+
const readable = await authorize({ admin, actor, data: { id }, effect: 'read', kayan });
|
|
148
|
+
const scoped = [{ id: { equals: id } }, readable, alive, admitted].filter(Boolean);
|
|
149
|
+
return (await store.find({ kayan: kayan.name, args: { limit: 1, page: 1, sort: { field: 'id', direction: 'asc' }, where: { and: scoped } } })).docs[0];
|
|
150
|
+
};
|
|
151
|
+
// Version and row ids share one uuid space, and a caller may choose a row's id,
|
|
152
|
+
// so an id that also names a row the actor sees is the row's. Looked for in the
|
|
153
|
+
// actor's scope only, or another tenant could block restores with a learned id.
|
|
154
|
+
if (version && await rowOf(data.id))
|
|
155
|
+
throw absent();
|
|
156
|
+
const row = await rowOf(parent, admits);
|
|
157
|
+
if (!row || (!version && row._status !== 'published'))
|
|
158
|
+
throw absent();
|
|
159
|
+
return { ...(version && changedColumns({ kayan, snapshot: revived(kayan, version.version), row, whole: kayan.versions.drafts && row._status !== 'published' })), id: parent };
|
|
160
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ResolvedField, RuntimeField } from '../mithaq/types.ts';
|
|
3
|
+
import type { Derives, FieldsOf } from '../asas/runner/steps.ts';
|
|
4
|
+
import type { CreateOf, IdOf, RowOf, UpdateOf } from '../asas/runner/write.ts';
|
|
5
|
+
export type ZodShape = (field: ResolvedField) => z.ZodType;
|
|
6
|
+
export type ZodRule<Value = never> = (args: {
|
|
7
|
+
shape: z.ZodType;
|
|
8
|
+
value: Value;
|
|
9
|
+
field: ResolvedField;
|
|
10
|
+
}) => z.ZodType;
|
|
11
|
+
type ZodTyped<Value> = z.ZodType<Value, Value>;
|
|
12
|
+
type ZodColumn<Value> = undefined extends Value ? z.ZodOptional<ZodTyped<Exclude<Value, undefined>>> : ZodTyped<Value>;
|
|
13
|
+
type ZodDerived<Shape, Config extends z.core.$ZodObjectConfig> = z.ZodObject<{
|
|
14
|
+
[Key in keyof Shape]-?: ZodColumn<Shape[Key]>;
|
|
15
|
+
}, Config>;
|
|
16
|
+
type ZodShapes<F extends Record<string, RuntimeField>> = {
|
|
17
|
+
create: ZodDerived<CreateOf<F, 'zod'>, z.core.$strict>;
|
|
18
|
+
update: ZodDerived<UpdateOf<F, 'zod'>, z.core.$strict>;
|
|
19
|
+
id: ZodDerived<IdOf<F, 'zod'>, z.core.$strict>;
|
|
20
|
+
byID: z.ZodObject<ZodDerived<IdOf<F, 'zod'>, z.core.$strict>['shape'] & {
|
|
21
|
+
depth: z.ZodOptional<z.ZodUnknown>;
|
|
22
|
+
}, z.core.$strict>;
|
|
23
|
+
row: ZodDerived<RowOf<F, 'zod'>, z.core.$strip>;
|
|
24
|
+
list: typeof listShape;
|
|
25
|
+
draft: z.ZodObject<{
|
|
26
|
+
[Key in keyof RowOf<F, 'zod'>]-?: z.ZodOptional<z.ZodNullable<ZodTyped<RowOf<F, 'zod'>[Key]>>>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
draftWrite: z.ZodObject<{
|
|
29
|
+
[Key in keyof RowOf<F, 'zod'>]-?: z.ZodOptional<z.ZodNullable<ZodTyped<RowOf<F, 'zod'>[Key]>>>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
};
|
|
32
|
+
declare const listShape: z.ZodObject<{
|
|
33
|
+
data: z.ZodOptional<z.ZodUnknown>;
|
|
34
|
+
depth: z.ZodOptional<z.ZodUnknown>;
|
|
35
|
+
limit: z.ZodOptional<z.ZodUnknown>;
|
|
36
|
+
page: z.ZodOptional<z.ZodUnknown>;
|
|
37
|
+
sort: z.ZodOptional<z.ZodUnknown>;
|
|
38
|
+
where: z.ZodOptional<z.ZodUnknown>;
|
|
39
|
+
}, z.core.$strict>;
|
|
40
|
+
interface ZodDerives extends Derives {
|
|
41
|
+
readonly shapes: ZodShapes<FieldsOf<this>>;
|
|
42
|
+
}
|
|
43
|
+
declare const target: {
|
|
44
|
+
readonly capability: 'schema';
|
|
45
|
+
readonly type: 'zod';
|
|
46
|
+
readonly idKinds: ("number" | "text" | "uuid")[];
|
|
47
|
+
readonly kinds: {
|
|
48
|
+
text: (field?: ResolvedField, element?: boolean) => z.ZodEnum<{
|
|
49
|
+
[x: string]: string;
|
|
50
|
+
}> | z.ZodString;
|
|
51
|
+
email: (field?: ResolvedField) => z.ZodEmail;
|
|
52
|
+
number: (field?: ResolvedField) => z.ZodGUID | z.ZodNumber | z.ZodString;
|
|
53
|
+
integer: (field?: ResolvedField) => z.ZodInt32;
|
|
54
|
+
decimal: (field?: ResolvedField) => z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
55
|
+
checkbox: (field?: ResolvedField) => z.ZodBoolean;
|
|
56
|
+
datetime: (field?: ResolvedField) => z.ZodDate;
|
|
57
|
+
date: (field?: ResolvedField) => z.ZodISODate;
|
|
58
|
+
textArray: (field?: ResolvedField) => z.ZodArray<z.ZodEnum<{
|
|
59
|
+
[x: string]: string;
|
|
60
|
+
}> | z.ZodString>;
|
|
61
|
+
uuid: (field?: ResolvedField) => z.ZodGUID;
|
|
62
|
+
relationship: (field: ResolvedField) => z.ZodArray<z.ZodGUID | z.ZodInt32 | z.ZodString> | z.ZodGUID | z.ZodInt32 | z.ZodString;
|
|
63
|
+
join: (field?: ResolvedField) => z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
64
|
+
};
|
|
65
|
+
readonly rules: {
|
|
66
|
+
unique: ({ shape }: {
|
|
67
|
+
shape: z.ZodType;
|
|
68
|
+
value: boolean;
|
|
69
|
+
field: ResolvedField;
|
|
70
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
71
|
+
index: ({ shape }: {
|
|
72
|
+
shape: z.ZodType;
|
|
73
|
+
value: boolean;
|
|
74
|
+
field: ResolvedField;
|
|
75
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
76
|
+
onDelete: ({ shape }: {
|
|
77
|
+
shape: z.ZodType;
|
|
78
|
+
value: import("../base/kinds.ts").OnDelete;
|
|
79
|
+
field: ResolvedField;
|
|
80
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
81
|
+
providedBy: ({ shape }: {
|
|
82
|
+
shape: z.ZodType;
|
|
83
|
+
value: Readonly<{
|
|
84
|
+
create?: import("../base/rules.ts").Provider;
|
|
85
|
+
update?: import("../base/rules.ts").Provider;
|
|
86
|
+
}>;
|
|
87
|
+
field: ResolvedField;
|
|
88
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
89
|
+
minLength: ({ shape, value: length, field }: {
|
|
90
|
+
shape: z.ZodType;
|
|
91
|
+
value: number;
|
|
92
|
+
field: ResolvedField;
|
|
93
|
+
}) => z.ZodString;
|
|
94
|
+
maxLength: ({ shape, value: length, field }: {
|
|
95
|
+
shape: z.ZodType;
|
|
96
|
+
value: number;
|
|
97
|
+
field: ResolvedField;
|
|
98
|
+
}) => z.ZodString;
|
|
99
|
+
pattern: ({ shape, value: expression, field }: {
|
|
100
|
+
shape: z.ZodType;
|
|
101
|
+
value: RegExp;
|
|
102
|
+
field: ResolvedField;
|
|
103
|
+
}) => z.ZodString;
|
|
104
|
+
precision: ({ shape, value: precision, field }: {
|
|
105
|
+
shape: z.ZodType;
|
|
106
|
+
value: number;
|
|
107
|
+
field: ResolvedField;
|
|
108
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
109
|
+
scale: ({ shape, value: scale, field }: {
|
|
110
|
+
shape: z.ZodType;
|
|
111
|
+
value: number;
|
|
112
|
+
field: ResolvedField;
|
|
113
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
114
|
+
min: ({ shape, value, field }: {
|
|
115
|
+
shape: z.ZodType;
|
|
116
|
+
value: string | number;
|
|
117
|
+
field: ResolvedField;
|
|
118
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
119
|
+
max: ({ shape, value, field }: {
|
|
120
|
+
shape: z.ZodType;
|
|
121
|
+
value: string | number;
|
|
122
|
+
field: ResolvedField;
|
|
123
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
124
|
+
minRows: ({ shape, value: rows, field }: {
|
|
125
|
+
shape: z.ZodType;
|
|
126
|
+
value: number;
|
|
127
|
+
field: ResolvedField;
|
|
128
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
129
|
+
maxRows: ({ shape, value: rows, field }: {
|
|
130
|
+
shape: z.ZodType;
|
|
131
|
+
value: number;
|
|
132
|
+
field: ResolvedField;
|
|
133
|
+
}) => z.ZodArray<z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
134
|
+
trim: ({ shape, value: on, field }: {
|
|
135
|
+
shape: z.ZodType;
|
|
136
|
+
value: boolean;
|
|
137
|
+
field: ResolvedField;
|
|
138
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
139
|
+
uppercase: ({ shape, value: on, field }: {
|
|
140
|
+
shape: z.ZodType;
|
|
141
|
+
value: boolean;
|
|
142
|
+
field: ResolvedField;
|
|
143
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
144
|
+
lowercase: ({ shape, value: on, field }: {
|
|
145
|
+
shape: z.ZodType;
|
|
146
|
+
value: boolean;
|
|
147
|
+
field: ResolvedField;
|
|
148
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
149
|
+
localized: ({ shape, value: on, field }: {
|
|
150
|
+
shape: z.ZodType;
|
|
151
|
+
value: boolean;
|
|
152
|
+
field: ResolvedField;
|
|
153
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
154
|
+
required: ({ shape, value: on, field }: {
|
|
155
|
+
shape: z.ZodType;
|
|
156
|
+
value: boolean;
|
|
157
|
+
field: ResolvedField;
|
|
158
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
159
|
+
defaultValue: ({ shape }: {
|
|
160
|
+
shape: z.ZodType;
|
|
161
|
+
value: string | number | boolean | Date | readonly unknown[] | import("../index.ts").Ref;
|
|
162
|
+
field: ResolvedField;
|
|
163
|
+
}) => z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
164
|
+
defaultNow: ({ shape, value: on }: {
|
|
165
|
+
shape: z.ZodType;
|
|
166
|
+
value: boolean;
|
|
167
|
+
field: ResolvedField;
|
|
168
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
169
|
+
nowOnUpdate: ({ shape }: {
|
|
170
|
+
shape: z.ZodType;
|
|
171
|
+
value: boolean;
|
|
172
|
+
field: ResolvedField;
|
|
173
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
174
|
+
};
|
|
175
|
+
readonly derive: <F extends Record<string, ResolvedField>>(this: {
|
|
176
|
+
kinds: Record<string, ZodShape>;
|
|
177
|
+
rules: Record<string, ZodRule>;
|
|
178
|
+
}, fields: F) => ZodShapes<F>;
|
|
179
|
+
};
|
|
180
|
+
/** The zod schema target. */
|
|
181
|
+
export declare const zodTarget: typeof target & {
|
|
182
|
+
readonly derives?: ZodDerives;
|
|
183
|
+
};
|
|
184
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { storedRichText } from '../rich-text/rich-text.js';
|
|
3
|
+
import { providerOf } from '../base/rules.js';
|
|
4
|
+
import { idKinds as zodIdKinds, kinds as zodKinds } from './kinds.js';
|
|
5
|
+
import { implementationOf } from '../mithaq/rukn.js';
|
|
6
|
+
import { rules as zodRules } from './rules.js';
|
|
7
|
+
// Every rule is applied here and nowhere else. A field's `targets.zod` is its
|
|
8
|
+
// whole shape, rules skipped.
|
|
9
|
+
const fieldShapes = (fields, shapes, rules) => Object.fromEntries(Object.values(fields).map((field) => {
|
|
10
|
+
const override = (field.schemaTarget && field.targets?.[field.schemaTarget]);
|
|
11
|
+
if (override)
|
|
12
|
+
return [field.name, override];
|
|
13
|
+
// The project's own entry, then the kind's own, then the shipped table.
|
|
14
|
+
let shape = implementationOf({ table: shapes, shipped: zodKinds, name: field.kind, own: field.kindTargets, type: 'zod' })(field);
|
|
15
|
+
const fieldRules = field.rules ?? {};
|
|
16
|
+
for (const rule of Object.keys(rules)) {
|
|
17
|
+
if (rule in fieldRules) {
|
|
18
|
+
shape = rules[rule]({ shape, value: fieldRules[rule], field });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// An id is filled by the database or by whoever owns the table, so a write never has to carry one.
|
|
22
|
+
return [field.name, field.name === 'id' ? shape.optional() : shape];
|
|
23
|
+
}));
|
|
24
|
+
// Strict, so an unknown key is refused at the boundary rather than stripped.
|
|
25
|
+
const kayanSchema = (built) => z.strictObject(built);
|
|
26
|
+
// A field the client does not supply is absent, not optional, so the strict
|
|
27
|
+
// object refuses a caller that sends it rather than discarding the value.
|
|
28
|
+
const supplied = (built, fields, on) => Object.fromEntries(Object.values(fields)
|
|
29
|
+
.filter((field) => field.name === 'id' || providerOf(field, on) === 'client')
|
|
30
|
+
.map((field) => [field.name, built[field.name]]));
|
|
31
|
+
const outputShape = (shape) => {
|
|
32
|
+
const hasTransform = (value, seen = new WeakSet()) => {
|
|
33
|
+
if (!value || typeof value !== 'object' || seen.has(value))
|
|
34
|
+
return false;
|
|
35
|
+
seen.add(value);
|
|
36
|
+
if (value instanceof z.ZodType) {
|
|
37
|
+
const definition = value._def;
|
|
38
|
+
if (definition?.type === 'transform')
|
|
39
|
+
return true;
|
|
40
|
+
return hasTransform(definition, seen);
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value))
|
|
43
|
+
return value.some((one) => hasTransform(one, seen));
|
|
44
|
+
return Object.values(value).some((one) => hasTransform(one, seen));
|
|
45
|
+
};
|
|
46
|
+
const definition = shape._def;
|
|
47
|
+
if (definition?.type === 'optional' && definition.innerType)
|
|
48
|
+
return outputShape(definition.innerType);
|
|
49
|
+
if (definition?.type === 'nullable' && definition.innerType) {
|
|
50
|
+
return outputShape(definition.innerType).nullable();
|
|
51
|
+
}
|
|
52
|
+
if (['catch', 'default'].includes(String(definition?.type)) && definition?.innerType) {
|
|
53
|
+
return outputShape(definition.innerType);
|
|
54
|
+
}
|
|
55
|
+
if (definition?.type === 'pipe' && definition.out)
|
|
56
|
+
return outputShape(definition.out);
|
|
57
|
+
if (hasTransform(shape)) {
|
|
58
|
+
throw new Error('A zod transform override needs an output schema: use `.pipe(schema)`.');
|
|
59
|
+
}
|
|
60
|
+
return shape;
|
|
61
|
+
};
|
|
62
|
+
// A `join` is optional rather than nullable: a read with no depth hands back no
|
|
63
|
+
// value for it, and a nullable key would demand an explicit `null`.
|
|
64
|
+
const rowSchema = (schema, fields) => z.object(Object.fromEntries(Object.entries(schema.shape).map(([name, shape]) => [
|
|
65
|
+
name,
|
|
66
|
+
(() => {
|
|
67
|
+
const required = outputShape(shape);
|
|
68
|
+
if (fields[name]?.kind === 'join')
|
|
69
|
+
return required.optional();
|
|
70
|
+
return name === 'id' || fields[name]?.rules?.required ? required : required.nullable();
|
|
71
|
+
})(),
|
|
72
|
+
])));
|
|
73
|
+
// Strict: a misspelled `wher` stripped would answer every row unfiltered.
|
|
74
|
+
const listShape = z.strictObject({
|
|
75
|
+
data: z.unknown().optional(),
|
|
76
|
+
depth: z.unknown().optional(),
|
|
77
|
+
limit: z.unknown().optional(),
|
|
78
|
+
page: z.unknown().optional(),
|
|
79
|
+
sort: z.unknown().optional(),
|
|
80
|
+
where: z.unknown().optional(),
|
|
81
|
+
});
|
|
82
|
+
// The cast puts the mapped return type on: nothing inside can prove what `ZodDerives` states.
|
|
83
|
+
const target = {
|
|
84
|
+
capability: 'schema',
|
|
85
|
+
type: 'zod',
|
|
86
|
+
idKinds: zodIdKinds,
|
|
87
|
+
kinds: zodKinds,
|
|
88
|
+
rules: zodRules,
|
|
89
|
+
derive(fields) {
|
|
90
|
+
const built = fieldShapes(fields, this.kinds, this.rules);
|
|
91
|
+
// A stored row is read against its kinds alone, with no rule and no `options`,
|
|
92
|
+
// so a rule or option added after rows were stored refuses their next save, never their read.
|
|
93
|
+
const kinds = Object.fromEntries(Object.entries(fields).map(([name, field]) => [
|
|
94
|
+
name,
|
|
95
|
+
{ ...field, options: undefined, ...(field.kind === 'richText' && { richText: storedRichText }) },
|
|
96
|
+
]));
|
|
97
|
+
const stored = fieldShapes(kinds, this.kinds, { localized: this.rules.localized });
|
|
98
|
+
const drafted = fieldShapes(Object.fromEntries(Object.entries(kinds).map(([name, field]) => [name, field.kind === 'richText' ? { ...field, richText: fields[name].richText, rules: { ...field.rules, required: false } } : field])), this.kinds, { localized: this.rules.localized });
|
|
99
|
+
const create = kayanSchema(supplied(built, fields, 'create'));
|
|
100
|
+
const changeable = kayanSchema(supplied(built, fields, 'update'));
|
|
101
|
+
const update = 'id' in fields ? changeable.partial().required({ id: true }) : changeable.partial();
|
|
102
|
+
// `pick` keeps the strict object strict, so every other key is refused.
|
|
103
|
+
const id = 'id' in fields ? update.pick({ id: true }) : z.strictObject({});
|
|
104
|
+
return {
|
|
105
|
+
create,
|
|
106
|
+
update,
|
|
107
|
+
id,
|
|
108
|
+
byID: id.extend({ depth: z.unknown().optional() }),
|
|
109
|
+
// Built from every field: a row is what the store holds, not what a caller may send.
|
|
110
|
+
row: rowSchema(kayanSchema(stored), fields),
|
|
111
|
+
list: listShape,
|
|
112
|
+
// Stripped rather than strict: the runner has already refused unknown keys, and
|
|
113
|
+
// a version stored before a field was dropped still reads.
|
|
114
|
+
draft: z.object(Object.fromEntries(Object.entries(stored).map(([name, shape]) => [name, shape.nullable().optional()]))),
|
|
115
|
+
// A draft may leave out what `required` asks for, but its rich text is still checked.
|
|
116
|
+
draftWrite: z.object(Object.fromEntries(Object.entries(drafted).map(([name, shape]) => [name, shape.nullable().optional()]))),
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
/** The zod schema target. */
|
|
121
|
+
export const zodTarget = target;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ResolvedField } from '../mithaq/types.ts';
|
|
3
|
+
export declare const errorOf: (field?: ResolvedField, element?: boolean) => ((issue: {
|
|
4
|
+
input: unknown;
|
|
5
|
+
}) => string) | undefined;
|
|
6
|
+
type KindError = ReturnType<typeof errorOf>;
|
|
7
|
+
declare const idShapes: {
|
|
8
|
+
number: (error?: KindError) => z.ZodInt32;
|
|
9
|
+
text: (error?: KindError) => z.ZodString;
|
|
10
|
+
uuid: (error?: KindError) => z.ZodGUID;
|
|
11
|
+
};
|
|
12
|
+
/** The kinds this target shapes an id of, the same three the store's columns are. */
|
|
13
|
+
export declare const idKinds: (keyof typeof idShapes)[];
|
|
14
|
+
declare const textShape: (field?: ResolvedField, element?: boolean) => z.ZodEnum<{
|
|
15
|
+
[x: string]: string;
|
|
16
|
+
}> | z.ZodString;
|
|
17
|
+
/** One shape per kind, the same record shape as the adapter's kinds. */
|
|
18
|
+
export declare const kinds: {
|
|
19
|
+
text: typeof textShape;
|
|
20
|
+
email: (field?: ResolvedField) => z.ZodEmail;
|
|
21
|
+
number: (field?: ResolvedField) => z.ZodGUID | z.ZodNumber | z.ZodString;
|
|
22
|
+
integer: (field?: ResolvedField) => z.ZodInt32;
|
|
23
|
+
decimal: (field?: ResolvedField) => z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
24
|
+
checkbox: (field?: ResolvedField) => z.ZodBoolean;
|
|
25
|
+
datetime: (field?: ResolvedField) => z.ZodDate;
|
|
26
|
+
date: (field?: ResolvedField) => z.ZodISODate;
|
|
27
|
+
textArray: (field?: ResolvedField) => z.ZodArray<z.ZodEnum<{
|
|
28
|
+
[x: string]: string;
|
|
29
|
+
}> | z.ZodString>;
|
|
30
|
+
uuid: (field?: ResolvedField) => z.ZodGUID;
|
|
31
|
+
relationship: (field: ResolvedField) => z.ZodArray<z.ZodGUID | z.ZodInt32 | z.ZodString> | z.ZodGUID | z.ZodInt32 | z.ZodString;
|
|
32
|
+
join: (field?: ResolvedField) => z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { isDecimal } from '../base/decimal.js';
|
|
3
|
+
// A missing key, or a `null` on a required field, is the `required` rule refusing;
|
|
4
|
+
// anything else is the kind refusing the value. A field with no kayan keeps zod's text.
|
|
5
|
+
export const errorOf = (field, element = false) => field?.kayan
|
|
6
|
+
? (issue) => issue.input === undefined || (issue.input === null && !element && field.rules?.required === true)
|
|
7
|
+
? `${field.kayan}.${field.name}.required`
|
|
8
|
+
: `${field.kayan}.${field.name}.invalid`
|
|
9
|
+
: undefined;
|
|
10
|
+
const idShapes = {
|
|
11
|
+
// A number id is an int4 `serial`, so a fraction or a value past its range is a 400, not a Postgres 500.
|
|
12
|
+
number: (error) => z.int32({ error }),
|
|
13
|
+
text: (error) => z.string({ error }),
|
|
14
|
+
// `guid`, not `uuid`: `z.uuid()` also demands an RFC version and variant, which Postgres does not.
|
|
15
|
+
uuid: (error) => z.guid({ error }),
|
|
16
|
+
};
|
|
17
|
+
/** The kinds this target shapes an id of, the same three the store's columns are. */
|
|
18
|
+
export const idKinds = Object.keys(idShapes);
|
|
19
|
+
const idShape = (kind, error) => idShapes[kind](error);
|
|
20
|
+
// Narrowed to the field's `options` on the server, not only in the browser.
|
|
21
|
+
const textShape = (field, element = false) => {
|
|
22
|
+
const values = field?.options?.map((option) => option.value);
|
|
23
|
+
return values?.length ? z.enum(values, { error: errorOf(field, element) }) : z.string({ error: errorOf(field, element) });
|
|
24
|
+
};
|
|
25
|
+
/** One shape per kind, the same record shape as the adapter's kinds. */
|
|
26
|
+
export const kinds = {
|
|
27
|
+
text: textShape,
|
|
28
|
+
email: (field) => z.email({ error: errorOf(field) }),
|
|
29
|
+
// The id is the `serial` column `idShapes` says it is; any other number is a
|
|
30
|
+
// `numeric` column, which takes every finite number.
|
|
31
|
+
number: (field) => (field?.name === 'id' ? idShape('number', errorOf(field)) : z.number({ error: errorOf(field) })),
|
|
32
|
+
// A Postgres `integer`, the same int4 a number id is.
|
|
33
|
+
integer: (field) => z.int32({ error: errorOf(field) }),
|
|
34
|
+
// A JS number is refused: it may already have lost a digit. `abort`, so no
|
|
35
|
+
// rule after it reads text that is no decimal.
|
|
36
|
+
decimal: (field) => z.string({ error: errorOf(field) }).refine(isDecimal, { error: errorOf(field), abort: true }),
|
|
37
|
+
checkbox: (field) => z.boolean({ error: errorOf(field) }),
|
|
38
|
+
datetime: (field) => z.date({ error: errorOf(field) }),
|
|
39
|
+
// A string, so a `Date` is refused rather than truncated to whichever day its zone makes of it.
|
|
40
|
+
date: (field) => z.iso.date({ error: errorOf(field) }),
|
|
41
|
+
// The same error on each element, so a bad element reads as the field's own sentence.
|
|
42
|
+
textArray: (field) => z.array(textShape(field, true), { error: errorOf(field) }),
|
|
43
|
+
uuid: (field) => z.guid({ error: errorOf(field) }),
|
|
44
|
+
relationship: (field) => field.hasMany
|
|
45
|
+
? z.array(idShape(field.target.fields.id.kind, errorOf(field, true)), { error: errorOf(field) })
|
|
46
|
+
: idShape(field.target.fields.id.kind, errorOf(field)),
|
|
47
|
+
// Left loose: the child's row shape may not be derived yet, and the child's own read already parsed the rows.
|
|
48
|
+
join: (field) => z.array(z.record(z.string(), z.unknown()), { error: errorOf(field) }),
|
|
49
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ResolvedField } from '../mithaq/types.ts';
|
|
3
|
+
export declare const rules: {
|
|
4
|
+
/** The database enforces it, and only the database can. */
|
|
5
|
+
unique: ({ shape }: {
|
|
6
|
+
shape: z.ZodType;
|
|
7
|
+
value: boolean;
|
|
8
|
+
field: ResolvedField;
|
|
9
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
10
|
+
/** Nothing about reading rows faster changes what a write must look like. */
|
|
11
|
+
index: ({ shape }: {
|
|
12
|
+
shape: z.ZodType;
|
|
13
|
+
value: boolean;
|
|
14
|
+
field: ResolvedField;
|
|
15
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
16
|
+
/** A rule about deleting the other row, and a write is not a delete. */
|
|
17
|
+
onDelete: ({ shape }: {
|
|
18
|
+
shape: z.ZodType;
|
|
19
|
+
value: import("../index.ts").OnDelete;
|
|
20
|
+
field: ResolvedField;
|
|
21
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
22
|
+
providedBy: ({ shape }: {
|
|
23
|
+
shape: z.ZodType;
|
|
24
|
+
value: Readonly<{
|
|
25
|
+
create?: import("../base/rules.ts").Provider;
|
|
26
|
+
update?: import("../base/rules.ts").Provider;
|
|
27
|
+
}>;
|
|
28
|
+
field: ResolvedField;
|
|
29
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
30
|
+
minLength: ({ shape, value: length, field }: {
|
|
31
|
+
shape: z.ZodType;
|
|
32
|
+
value: number;
|
|
33
|
+
field: ResolvedField;
|
|
34
|
+
}) => z.ZodString;
|
|
35
|
+
maxLength: ({ shape, value: length, field }: {
|
|
36
|
+
shape: z.ZodType;
|
|
37
|
+
value: number;
|
|
38
|
+
field: ResolvedField;
|
|
39
|
+
}) => z.ZodString;
|
|
40
|
+
pattern: ({ shape, value: expression, field }: {
|
|
41
|
+
shape: z.ZodType;
|
|
42
|
+
value: RegExp;
|
|
43
|
+
field: ResolvedField;
|
|
44
|
+
}) => z.ZodString;
|
|
45
|
+
precision: ({ shape, value: precision, field }: {
|
|
46
|
+
shape: z.ZodType;
|
|
47
|
+
value: number;
|
|
48
|
+
field: ResolvedField;
|
|
49
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
50
|
+
scale: ({ shape, value: scale, field }: {
|
|
51
|
+
shape: z.ZodType;
|
|
52
|
+
value: number;
|
|
53
|
+
field: ResolvedField;
|
|
54
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
55
|
+
min: ({ shape, value, field }: {
|
|
56
|
+
shape: z.ZodType;
|
|
57
|
+
value: string | number;
|
|
58
|
+
field: ResolvedField;
|
|
59
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
60
|
+
max: ({ shape, value, field }: {
|
|
61
|
+
shape: z.ZodType;
|
|
62
|
+
value: string | number;
|
|
63
|
+
field: ResolvedField;
|
|
64
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
65
|
+
minRows: ({ shape, value: rows, field }: {
|
|
66
|
+
shape: z.ZodType;
|
|
67
|
+
value: number;
|
|
68
|
+
field: ResolvedField;
|
|
69
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
70
|
+
maxRows: ({ shape, value: rows, field }: {
|
|
71
|
+
shape: z.ZodType;
|
|
72
|
+
value: number;
|
|
73
|
+
field: ResolvedField;
|
|
74
|
+
}) => z.ZodArray<z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
75
|
+
trim: ({ shape, value: on, field }: {
|
|
76
|
+
shape: z.ZodType;
|
|
77
|
+
value: boolean;
|
|
78
|
+
field: ResolvedField;
|
|
79
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
80
|
+
uppercase: ({ shape, value: on, field }: {
|
|
81
|
+
shape: z.ZodType;
|
|
82
|
+
value: boolean;
|
|
83
|
+
field: ResolvedField;
|
|
84
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
85
|
+
lowercase: ({ shape, value: on, field }: {
|
|
86
|
+
shape: z.ZodType;
|
|
87
|
+
value: boolean;
|
|
88
|
+
field: ResolvedField;
|
|
89
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
90
|
+
localized: ({ shape, value: on, field }: {
|
|
91
|
+
shape: z.ZodType;
|
|
92
|
+
value: boolean;
|
|
93
|
+
field: ResolvedField;
|
|
94
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
95
|
+
required: ({ shape, value: on, field }: {
|
|
96
|
+
shape: z.ZodType;
|
|
97
|
+
value: boolean;
|
|
98
|
+
field: ResolvedField;
|
|
99
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
100
|
+
defaultValue: ({ shape }: {
|
|
101
|
+
shape: z.ZodType;
|
|
102
|
+
value: string | number | boolean | Date | readonly unknown[] | import("../index.ts").Ref;
|
|
103
|
+
field: ResolvedField;
|
|
104
|
+
}) => z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
105
|
+
/** The moment the defaults step writes, which leaves the key optional as a literal does. */
|
|
106
|
+
defaultNow: ({ shape, value: on }: {
|
|
107
|
+
shape: z.ZodType;
|
|
108
|
+
value: boolean;
|
|
109
|
+
field: ResolvedField;
|
|
110
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
111
|
+
/** Filled on update by the defaults step, and never client-supplied there, so nothing to shape. */
|
|
112
|
+
nowOnUpdate: ({ shape }: {
|
|
113
|
+
shape: z.ZodType;
|
|
114
|
+
value: boolean;
|
|
115
|
+
field: ResolvedField;
|
|
116
|
+
}) => z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
117
|
+
};
|