@ryuzaki13/react-foundation-lib 2.25.0 → 2.26.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/indexed-records/createIndexedRecordsSchema.d.ts +11 -0
- package/dist/indexed-records/createIndexedRecordsSchema.d.ts.map +1 -0
- package/dist/indexed-records/indexedRecordsSchemaTypes.d.ts +7 -0
- package/dist/indexed-records/indexedRecordsSchemaTypes.d.ts.map +1 -0
- package/dist/indexed-records/schema.d.ts +3 -0
- package/dist/indexed-records/schema.d.ts.map +1 -0
- package/dist/indexed-records/schema.js +45 -0
- package/dist/indexed-records/schema.js.map +1 -0
- package/package.json +5 -1
- package/src/indexed-records/README.md +34 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type IndexedRecordsSchemaOptions } from "./indexedRecordsSchemaTypes";
|
|
3
|
+
import { type IndexedRecords } from "./indexedRecordsTypes";
|
|
4
|
+
/**
|
|
5
|
+
* Проверяет доставленный позиционный индекс, не восстанавливая его по items.
|
|
6
|
+
* Предметная itemsSchema вызывается один раз и сохраняет свой порядок/ссылки;
|
|
7
|
+
* она же обеспечивает неизменность массива, записей и вложенных значений.
|
|
8
|
+
* Фабрика создаёт и замораживает только собственные оболочку и numeric index.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createIndexedRecordsSchema<T>({ itemsSchema, getId }: IndexedRecordsSchemaOptions<T>): z.ZodType<IndexedRecords<T>>;
|
|
11
|
+
//# sourceMappingURL=createIndexedRecordsSchema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createIndexedRecordsSchema.d.ts","sourceRoot":"","sources":["../../src/indexed-records/createIndexedRecordsSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC/E,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAa5D;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,2BAA2B,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CA4ClI"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type z } from "zod";
|
|
2
|
+
/** Предметная схема владеет записями, их неизменностью и канонической identity. */
|
|
3
|
+
export type IndexedRecordsSchemaOptions<T> = Readonly<{
|
|
4
|
+
itemsSchema: z.ZodType<readonly T[]>;
|
|
5
|
+
getId: (item: T) => string;
|
|
6
|
+
}>;
|
|
7
|
+
//# sourceMappingURL=indexedRecordsSchemaTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexedRecordsSchemaTypes.d.ts","sourceRoot":"","sources":["../../src/indexed-records/indexedRecordsSchemaTypes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,mFAAmF;AACnF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI,QAAQ,CAAC;IACrD,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACrC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;CAC3B,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/indexed-records/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { i as e, r as t } from "../chunks/isRecord-DXDTW-jA.js";
|
|
2
|
+
import { z as n } from "zod";
|
|
3
|
+
//#region src/indexed-records/createIndexedRecordsSchema.ts
|
|
4
|
+
function r(n) {
|
|
5
|
+
if (!e(n) || !t(n) || !Object.hasOwn(n, "items") || !Object.hasOwn(n, "indexById")) return !1;
|
|
6
|
+
for (let e of Reflect.ownKeys(n)) {
|
|
7
|
+
let t = Object.getOwnPropertyDescriptor(n, e);
|
|
8
|
+
if (typeof e != "string" || !t?.enumerable || !("value" in t)) return !1;
|
|
9
|
+
}
|
|
10
|
+
return !0;
|
|
11
|
+
}
|
|
12
|
+
function i({ itemsSchema: i, getId: a }) {
|
|
13
|
+
return n.custom(r, "Ожидалась структура индексированных записей без accessor и symbol.").pipe(n.strictObject({
|
|
14
|
+
items: i,
|
|
15
|
+
indexById: n.unknown()
|
|
16
|
+
})).transform((r, i) => {
|
|
17
|
+
if (!e(r.indexById) || !t(r.indexById)) return i.addIssue({
|
|
18
|
+
code: "custom",
|
|
19
|
+
path: ["indexById"],
|
|
20
|
+
message: "Ожидался позиционный словарь записей."
|
|
21
|
+
}), n.NEVER;
|
|
22
|
+
let o = Object.create(null), s = 0;
|
|
23
|
+
for (let e of Reflect.ownKeys(r.indexById)) {
|
|
24
|
+
let t = Object.getOwnPropertyDescriptor(r.indexById, e), c = t?.value;
|
|
25
|
+
if (typeof e != "string" || !t?.enumerable || !("value" in t) || typeof c != "number" || !Number.isSafeInteger(c) || c < 0 || c >= r.items.length || !Object.hasOwn(r.items, c) || a(r.items[c]) !== e) return i.addIssue({
|
|
26
|
+
code: "custom",
|
|
27
|
+
path: ["indexById"],
|
|
28
|
+
message: "Индекс не соответствует записям."
|
|
29
|
+
}), n.NEVER;
|
|
30
|
+
o[e] = c, s += 1;
|
|
31
|
+
}
|
|
32
|
+
return s === r.items.length ? Object.freeze({
|
|
33
|
+
items: r.items,
|
|
34
|
+
indexById: Object.freeze(o)
|
|
35
|
+
}) : (i.addIssue({
|
|
36
|
+
code: "custom",
|
|
37
|
+
path: ["indexById"],
|
|
38
|
+
message: "Позиционный индекс неполон."
|
|
39
|
+
}), n.NEVER);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { i as createIndexedRecordsSchema };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../src/indexed-records/createIndexedRecordsSchema.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport { isPlainObject, isRecord } from \"../validators/isRecord\";\n\nimport { type IndexedRecordsSchemaOptions } from \"./indexedRecordsSchemaTypes\";\nimport { type IndexedRecords } from \"./indexedRecordsTypes\";\n\n/** Не позволяет Zod прочитать accessor или проигнорировать symbol на wire-оболочке. */\nfunction hasIndexedRecordsWireDataProperties(value: unknown): value is Record<string, unknown> {\n\tif (!isRecord(value) || !isPlainObject(value)) return false;\n\tif (!Object.hasOwn(value, \"items\") || !Object.hasOwn(value, \"indexById\")) return false;\n\tfor (const key of Reflect.ownKeys(value)) {\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, key);\n\t\tif (typeof key !== \"string\" || !descriptor?.enumerable || !(\"value\" in descriptor)) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Проверяет доставленный позиционный индекс, не восстанавливая его по items.\n * Предметная itemsSchema вызывается один раз и сохраняет свой порядок/ссылки;\n * она же обеспечивает неизменность массива, записей и вложенных значений.\n * Фабрика создаёт и замораживает только собственные оболочку и numeric index.\n */\nexport function createIndexedRecordsSchema<T>({ itemsSchema, getId }: IndexedRecordsSchemaOptions<T>): z.ZodType<IndexedRecords<T>> {\n\treturn z\n\t\t.custom<Record<string, unknown>>(\n\t\t\thasIndexedRecordsWireDataProperties,\n\t\t\t\"Ожидалась структура индексированных записей без accessor и symbol.\"\n\t\t)\n\t\t.pipe(z.strictObject({ items: itemsSchema, indexById: z.unknown() }))\n\t\t.transform((wire, context): IndexedRecords<T> => {\n\t\t\tif (!isRecord(wire.indexById) || !isPlainObject(wire.indexById)) {\n\t\t\t\tcontext.addIssue({ code: \"custom\", path: [\"indexById\"], message: \"Ожидался позиционный словарь записей.\" });\n\t\t\t\treturn z.NEVER;\n\t\t\t}\n\n\t\t\t// Проверка и копирование доставленных чисел происходят одним проходом.\n\t\t\t// z.record здесь не применяется: он удаляет собственный __proto__.\n\t\t\tconst indexById = Object.create(null) as Record<string, number>;\n\t\t\tlet entries = 0;\n\t\t\tfor (const id of Reflect.ownKeys(wire.indexById)) {\n\t\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(wire.indexById, id);\n\t\t\t\tconst position: unknown = descriptor?.value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof id !== \"string\" ||\n\t\t\t\t\t!descriptor?.enumerable ||\n\t\t\t\t\t!(\"value\" in descriptor) ||\n\t\t\t\t\ttypeof position !== \"number\" ||\n\t\t\t\t\t!Number.isSafeInteger(position) ||\n\t\t\t\t\tposition < 0 ||\n\t\t\t\t\tposition >= wire.items.length ||\n\t\t\t\t\t!Object.hasOwn(wire.items, position) ||\n\t\t\t\t\tgetId(wire.items[position]) !== id\n\t\t\t\t) {\n\t\t\t\t\tcontext.addIssue({ code: \"custom\", path: [\"indexById\"], message: \"Индекс не соответствует записям.\" });\n\t\t\t\t\treturn z.NEVER;\n\t\t\t\t}\n\t\t\t\tindexById[id] = position;\n\t\t\t\tentries += 1;\n\t\t\t}\n\t\t\tif (entries !== wire.items.length) {\n\t\t\t\tcontext.addIssue({ code: \"custom\", path: [\"indexById\"], message: \"Позиционный индекс неполон.\" });\n\t\t\t\treturn z.NEVER;\n\t\t\t}\n\n\t\t\treturn Object.freeze({ items: wire.items, indexById: Object.freeze(indexById) });\n\t\t});\n}\n"],"mappings":";;;AAQA,SAAS,EAAoC,GAAkD;CAE9F,IADI,CAAC,EAAS,CAAK,KAAK,CAAC,EAAc,CAAK,KACxC,CAAC,OAAO,OAAO,GAAO,OAAO,KAAK,CAAC,OAAO,OAAO,GAAO,WAAW,GAAG,OAAO;CACjF,KAAK,IAAM,KAAO,QAAQ,QAAQ,CAAK,GAAG;EACzC,IAAM,IAAa,OAAO,yBAAyB,GAAO,CAAG;EAC7D,IAAI,OAAO,KAAQ,YAAY,CAAC,GAAY,cAAc,EAAE,WAAW,IAAa,OAAO;CAC5F;CACA,OAAO;AACR;AAQA,SAAgB,EAA8B,EAAE,gBAAa,YAAuE;CACnI,OAAO,EACL,OACA,GACA,oEACD,CAAC,CACA,KAAK,EAAE,aAAa;EAAE,OAAO;EAAa,WAAW,EAAE,QAAQ;CAAE,CAAC,CAAC,CAAC,CACpE,WAAW,GAAM,MAA+B;EAChD,IAAI,CAAC,EAAS,EAAK,SAAS,KAAK,CAAC,EAAc,EAAK,SAAS,GAE7D,OADA,EAAQ,SAAS;GAAE,MAAM;GAAU,MAAM,CAAC,WAAW;GAAG,SAAS;EAAwC,CAAC,GACnG,EAAE;EAKV,IAAM,IAAY,OAAO,OAAO,IAAI,GAChC,IAAU;EACd,KAAK,IAAM,KAAM,QAAQ,QAAQ,EAAK,SAAS,GAAG;GACjD,IAAM,IAAa,OAAO,yBAAyB,EAAK,WAAW,CAAE,GAC/D,IAAoB,GAAY;GACtC,IACC,OAAO,KAAO,YACd,CAAC,GAAY,cACb,EAAE,WAAW,MACb,OAAO,KAAa,YACpB,CAAC,OAAO,cAAc,CAAQ,KAC9B,IAAW,KACX,KAAY,EAAK,MAAM,UACvB,CAAC,OAAO,OAAO,EAAK,OAAO,CAAQ,KACnC,EAAM,EAAK,MAAM,EAAS,MAAM,GAGhC,OADA,EAAQ,SAAS;IAAE,MAAM;IAAU,MAAM,CAAC,WAAW;IAAG,SAAS;GAAmC,CAAC,GAC9F,EAAE;GAGV,AADA,EAAU,KAAM,GAChB,KAAW;EACZ;EAMA,OALI,MAAY,EAAK,MAAM,SAKpB,OAAO,OAAO;GAAE,OAAO,EAAK;GAAO,WAAW,OAAO,OAAO,CAAS;EAAE,CAAC,KAJ9E,EAAQ,SAAS;GAAE,MAAM;GAAU,MAAM,CAAC,WAAW;GAAG,SAAS;EAA8B,CAAC,GACzF,EAAE;CAIX,CAAC;AACH"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuzaki13/react-foundation-lib",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.0",
|
|
4
4
|
"description": "Reusable React and TypeScript foundation utilities, hooks, and primitives.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ryuzaki13",
|
|
@@ -133,6 +133,10 @@
|
|
|
133
133
|
"types": "./dist/indexed-records/index.d.ts",
|
|
134
134
|
"import": "./dist/indexed-records/index.js"
|
|
135
135
|
},
|
|
136
|
+
"./indexed-records/schema": {
|
|
137
|
+
"types": "./dist/indexed-records/schema.d.ts",
|
|
138
|
+
"import": "./dist/indexed-records/schema.js"
|
|
139
|
+
},
|
|
136
140
|
"./media": {
|
|
137
141
|
"types": "./dist/media/index.d.ts",
|
|
138
142
|
"import": "./dist/media/index.js"
|
|
@@ -30,3 +30,37 @@ identity вызывает ошибку. Порядок и ссылки исхо
|
|
|
30
30
|
Восстановленные недоверенные данные должны пройти проверку у принимающей
|
|
31
31
|
границы. Наличие полей `items` и `indexById` само по себе не подтверждает
|
|
32
32
|
корректность позиций, полноту или уникальность каталога.
|
|
33
|
+
|
|
34
|
+
## Проверка доставленного индекса
|
|
35
|
+
|
|
36
|
+
Optional entry со схемой требует Zod 4. Прежний `indexed-records` entry не
|
|
37
|
+
импортирует его и сохраняет работу без Zod.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { z } from "zod";
|
|
41
|
+
import { createIndexedRecordsSchema } from "@ryuzaki13/react-foundation-lib/indexed-records/schema";
|
|
42
|
+
|
|
43
|
+
const recordsSchema = createIndexedRecordsSchema({
|
|
44
|
+
itemsSchema: z.array(z.strictObject({ id: z.string(), name: z.string() }).readonly()).readonly(),
|
|
45
|
+
getId: (item) => item.id
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const records = recordsSchema.parse(deliveredRecords);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Схема принимает только строгую оболочку `{ items, indexById }`. `itemsSchema`
|
|
52
|
+
проверяется один раз; её порядок, массив и записи переиспользуются без
|
|
53
|
+
дополнительной копии. Доставленный индекс проверяется и копируется одним
|
|
54
|
+
проходом. Каждая собственная identity должна указывать на целую допустимую
|
|
55
|
+
позицию своей записи; все записи должны быть покрыты ровно один раз.
|
|
56
|
+
Отсутствующие/лишние ключи, повторяющиеся identities, переставленные позиции,
|
|
57
|
+
accessor, symbol, нестандартный prototype и нечисловые значения отклоняются.
|
|
58
|
+
Строковые IDs, включая пустую строку, `__proto__` и числовые строки, не
|
|
59
|
+
нормализуются; числовой ID предмета можно преобразовать в `getId` через `String`.
|
|
60
|
+
|
|
61
|
+
Фабрика не строит индекс из массива, не сортирует записи и не знает их
|
|
62
|
+
предметной семантики. `getId` должен быть чистым и возвращать устойчивую
|
|
63
|
+
каноническую identity. Выходная оболочка и собственный null-prototype индекс
|
|
64
|
+
заморожены. Freeze массива, записей и вложенных объектов, а также межстрочные
|
|
65
|
+
предметные ограничения задаёт `itemsSchema` владельца. Generic recursive
|
|
66
|
+
freeze, второй parse записей, сертификаты, revision и cache сюда не входят.
|