soukai-bis 0.0.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/soukai-bis.d.ts +78 -0
- package/dist/soukai-bis.js +103 -0
- package/dist/soukai-bis.js.map +1 -0
- package/package.json +41 -0
- package/src/index.ts +2 -0
- package/src/models/Model.test.ts +44 -0
- package/src/models/Model.ts +49 -0
- package/src/models/concerns/serializes-to-rdf.ts +53 -0
- package/src/models/constants.ts +3 -0
- package/src/models/index.ts +3 -0
- package/src/models/schema.ts +62 -0
- package/src/testing/setup.ts +5 -0
- package/src/testing/stubs/User.schema.ts +14 -0
- package/src/testing/stubs/User.ts +3 -0
- package/src/zod/extensions/core.ts +38 -0
- package/src/zod/index.ts +19 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Constructor } from '@noeldemartin/utils';
|
|
2
|
+
import { JsonLD } from '@noeldemartin/solid-utils';
|
|
3
|
+
import { MagicObject } from '@noeldemartin/utils';
|
|
4
|
+
import { NamedNode } from '@rdfjs/types';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { ZodObject } from 'zod';
|
|
7
|
+
import { ZodType } from 'zod';
|
|
8
|
+
|
|
9
|
+
declare namespace coreExtensions {
|
|
10
|
+
export {
|
|
11
|
+
rdfProperty,
|
|
12
|
+
CustomMeta
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare interface CustomMeta {
|
|
17
|
+
rdfProperty?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export declare function defineSchema<T extends SchemaFields>(config: SchemaConfig<T>): SchemaModel<T>;
|
|
21
|
+
|
|
22
|
+
export declare class Model extends MagicObject {
|
|
23
|
+
static schema: Schema;
|
|
24
|
+
static<T extends typeof Model>(): T;
|
|
25
|
+
static<T extends typeof Model, K extends keyof T>(property: K): T[K];
|
|
26
|
+
url?: string;
|
|
27
|
+
private __attributes;
|
|
28
|
+
constructor(attributes?: Record<string, unknown>);
|
|
29
|
+
getAttributes(): Record<string, unknown>;
|
|
30
|
+
toJsonLD(): Promise<JsonLD>;
|
|
31
|
+
toTurtle(): Promise<string>;
|
|
32
|
+
protected __get(property: string): unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export declare function patchZod(): void;
|
|
36
|
+
|
|
37
|
+
declare function rdfProperty<T extends ZodType>(this: T): string | undefined;
|
|
38
|
+
|
|
39
|
+
declare function rdfProperty<T extends ZodType>(this: T, value: string): T;
|
|
40
|
+
|
|
41
|
+
export declare type Schema<T extends SchemaFields = SchemaFields> = {
|
|
42
|
+
fields: ZodObject<T>;
|
|
43
|
+
rdfContext: {
|
|
44
|
+
default: string;
|
|
45
|
+
} & Record<string, string>;
|
|
46
|
+
rdfClasses: NamedNode[];
|
|
47
|
+
rdfDefaultResourceHash: string;
|
|
48
|
+
rdfFieldProperties: Record<string, NamedNode>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export declare interface SchemaConfig<T extends SchemaFields> {
|
|
52
|
+
crdts?: boolean;
|
|
53
|
+
rdfContext?: string;
|
|
54
|
+
rdfClass?: string;
|
|
55
|
+
rdfDefaultResourceHash?: string;
|
|
56
|
+
fields: T;
|
|
57
|
+
relations?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export declare type SchemaFields = Record<string, ZodType>;
|
|
61
|
+
|
|
62
|
+
export declare type SchemaModel<T extends SchemaFields> = typeof Model & Constructor<z.infer<ZodObject<T>>>;
|
|
63
|
+
|
|
64
|
+
export declare type ZodCoreExtensions = typeof coreExtensions;
|
|
65
|
+
|
|
66
|
+
export { }
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
declare module 'zod' {
|
|
70
|
+
interface ZodType extends ZodCoreExtensions {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
declare module 'zod' {
|
|
76
|
+
interface GlobalMeta extends CustomMeta {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
var j = Object.defineProperty;
|
|
2
|
+
var x = (t, r, e) => r in t ? j(t, r, { enumerable: !0, configurable: !0, writable: !0, value: e }) : t[r] = e;
|
|
3
|
+
var f = (t, r, e) => x(t, typeof r != "symbol" ? r + "" : r, e);
|
|
4
|
+
import { ZodArray as C, ZodURL as R, ZodOptional as m, ZodDefault as b, object as P, z as F } from "zod";
|
|
5
|
+
import { MagicObject as O } from "@noeldemartin/utils";
|
|
6
|
+
import { expandIRI as c, RDFNamedNode as u, RDFQuad as p, RDFLiteral as w, quadsToJsonLD as g, quadsToTurtle as Z } from "@noeldemartin/solid-utils";
|
|
7
|
+
const A = c("rdf:type");
|
|
8
|
+
function d(t) {
|
|
9
|
+
return t instanceof m || t instanceof b ? d(t.def.innerType) : t;
|
|
10
|
+
}
|
|
11
|
+
function _(t, r) {
|
|
12
|
+
const e = d(r);
|
|
13
|
+
return e instanceof C ? (Array.isArray(t) ? t : t == null ? [] : [t]).flatMap((s) => _(s, e.def.element)) : e instanceof R ? [new u(String(t))] : [new w(String(t))];
|
|
14
|
+
}
|
|
15
|
+
function h(t) {
|
|
16
|
+
const { fields: r, rdfDefaultResourceHash: e, rdfClasses: n, rdfFieldProperties: s } = t.static().schema, i = new u(t.url ?? `#${e}`), o = [];
|
|
17
|
+
for (const a of n)
|
|
18
|
+
o.push(new p(i, A, a));
|
|
19
|
+
for (const [a, y] of Object.entries(t.getAttributes()))
|
|
20
|
+
for (const D of _(y, r.def.shape[a]))
|
|
21
|
+
o.push(new p(i, s[a], D));
|
|
22
|
+
return o;
|
|
23
|
+
}
|
|
24
|
+
class T extends O {
|
|
25
|
+
constructor(e = {}) {
|
|
26
|
+
super();
|
|
27
|
+
f(this, "__attributes");
|
|
28
|
+
if (this.static().isConjuring()) {
|
|
29
|
+
this.__attributes = {};
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.__attributes = this.static().schema.fields.parse(e);
|
|
33
|
+
}
|
|
34
|
+
static(e) {
|
|
35
|
+
return super.static(e);
|
|
36
|
+
}
|
|
37
|
+
getAttributes() {
|
|
38
|
+
return this.__attributes;
|
|
39
|
+
}
|
|
40
|
+
async toJsonLD() {
|
|
41
|
+
return g(h(this));
|
|
42
|
+
}
|
|
43
|
+
async toTurtle() {
|
|
44
|
+
return Z(h(this));
|
|
45
|
+
}
|
|
46
|
+
__get(e) {
|
|
47
|
+
return this.__attributes[e];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
f(T, "schema");
|
|
51
|
+
function V(t) {
|
|
52
|
+
var s;
|
|
53
|
+
const r = t.rdfContext ? { default: t.rdfContext } : { default: "solid" }, { default: e, ...n } = r;
|
|
54
|
+
return s = class extends T {
|
|
55
|
+
}, f(s, "schema", {
|
|
56
|
+
fields: P(t.fields),
|
|
57
|
+
rdfContext: r,
|
|
58
|
+
rdfDefaultResourceHash: t.rdfDefaultResourceHash ?? "it",
|
|
59
|
+
rdfClasses: t.rdfClass ? [
|
|
60
|
+
new u(
|
|
61
|
+
c(t.rdfClass, {
|
|
62
|
+
defaultPrefix: e,
|
|
63
|
+
extraContext: n
|
|
64
|
+
})
|
|
65
|
+
)
|
|
66
|
+
] : [],
|
|
67
|
+
rdfFieldProperties: Object.fromEntries(
|
|
68
|
+
Object.entries(t.fields).map(([i, o]) => [
|
|
69
|
+
i,
|
|
70
|
+
new u(
|
|
71
|
+
c(o.rdfProperty() ?? i, {
|
|
72
|
+
defaultPrefix: e,
|
|
73
|
+
extraContext: n
|
|
74
|
+
})
|
|
75
|
+
)
|
|
76
|
+
])
|
|
77
|
+
)
|
|
78
|
+
}), s;
|
|
79
|
+
}
|
|
80
|
+
function l(t, r) {
|
|
81
|
+
const e = t.meta();
|
|
82
|
+
if (e && r in e)
|
|
83
|
+
return e[r];
|
|
84
|
+
if (t instanceof b || t instanceof m)
|
|
85
|
+
return l(t.def.innerType, r);
|
|
86
|
+
}
|
|
87
|
+
function S(t) {
|
|
88
|
+
return typeof t != "string" ? l(this, "rdfProperty") : this.meta({ rdfProperty: t });
|
|
89
|
+
}
|
|
90
|
+
const L = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
91
|
+
__proto__: null,
|
|
92
|
+
rdfProperty: S
|
|
93
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
94
|
+
function q() {
|
|
95
|
+
for (const [t, r] of Object.entries(L))
|
|
96
|
+
F.ZodType.prototype[t] = r;
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
T as Model,
|
|
100
|
+
V as defineSchema,
|
|
101
|
+
q as patchZod
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=soukai-bis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"soukai-bis.js","sources":["../src/models/constants.ts","../src/models/concerns/serializes-to-rdf.ts","../src/models/Model.ts","../src/models/schema.ts","../src/zod/extensions/core.ts","../src/zod/index.ts"],"sourcesContent":["import { expandIRI } from '@noeldemartin/solid-utils';\n\nexport const RDF_TYPE = expandIRI('rdf:type');\n","import { ZodArray, ZodDefault, ZodOptional, ZodURL } from 'zod';\nimport { RDFLiteral, RDFNamedNode, RDFQuad } from '@noeldemartin/solid-utils';\nimport type { Quad, Quad_Object } from '@rdfjs/types';\nimport type { SomeType } from 'zod/v4/core';\n\nimport { RDF_TYPE } from 'soukai-bis/models/constants';\nimport type Model from 'soukai-bis/models/Model';\n\nfunction getFinalType(type: SomeType): SomeType {\n if (type instanceof ZodOptional) {\n return getFinalType(type.def.innerType);\n }\n\n if (type instanceof ZodDefault) {\n return getFinalType(type.def.innerType);\n }\n\n return type;\n}\n\nfunction castValue(value: unknown, type: SomeType): Quad_Object[] {\n const finalType = getFinalType(type);\n\n if (finalType instanceof ZodArray) {\n const arrayValue = Array.isArray(value) ? value : value === null || value === undefined ? [] : [value];\n\n return arrayValue.flatMap((item) => castValue(item, finalType.def.element));\n }\n\n if (finalType instanceof ZodURL) {\n return [new RDFNamedNode(String(value))];\n }\n\n return [new RDFLiteral(String(value))];\n}\n\nexport function serializeToRDF(model: Model): Quad[] {\n const { fields, rdfDefaultResourceHash, rdfClasses, rdfFieldProperties } = model.static().schema;\n const subject = new RDFNamedNode(model.url ?? `#${rdfDefaultResourceHash}`);\n const statements: Quad[] = [];\n\n for (const rdfClass of rdfClasses) {\n statements.push(new RDFQuad(subject, RDF_TYPE, rdfClass));\n }\n\n for (const [field, value] of Object.entries(model.getAttributes())) {\n for (const object of castValue(value, fields.def.shape[field])) {\n statements.push(new RDFQuad(subject, rdfFieldProperties[field], object));\n }\n }\n\n return statements;\n}\n","import { MagicObject } from '@noeldemartin/utils';\nimport { quadsToJsonLD, quadsToTurtle } from '@noeldemartin/solid-utils';\nimport type { JsonLD } from '@noeldemartin/solid-utils';\n\nimport { serializeToRDF } from './concerns/serializes-to-rdf';\nimport type { Schema } from './schema';\n\nexport default class Model extends MagicObject {\n\n public static schema: Schema;\n\n public static<T extends typeof Model>(): T;\n public static<T extends typeof Model, K extends keyof T>(property: K): T[K];\n public static<T extends typeof Model, K extends keyof T>(property?: K): T | T[K] {\n return super.static<T, K>(property as K);\n }\n\n declare public url?: string;\n private __attributes: Record<string, unknown>;\n\n public constructor(attributes: Record<string, unknown> = {}) {\n super();\n\n if (this.static().isConjuring()) {\n this.__attributes = {};\n\n return;\n }\n\n this.__attributes = this.static().schema.fields.parse(attributes);\n }\n\n public getAttributes(): Record<string, unknown> {\n return this.__attributes;\n }\n\n public async toJsonLD(): Promise<JsonLD> {\n return quadsToJsonLD(serializeToRDF(this));\n }\n\n public async toTurtle(): Promise<string> {\n return quadsToTurtle(serializeToRDF(this));\n }\n\n protected __get(property: string): unknown {\n return this.__attributes[property];\n }\n\n}\n","import { object } from 'zod';\nimport { RDFNamedNode, expandIRI } from '@noeldemartin/solid-utils';\nimport type { Constructor } from '@noeldemartin/utils';\nimport type { ZodObject, ZodType, z } from 'zod';\nimport type { NamedNode } from '@rdfjs/types';\n\nimport Model from './Model';\n\nexport type Schema<T extends SchemaFields = SchemaFields> = {\n fields: ZodObject<T>;\n rdfContext: { default: string } & Record<string, string>;\n rdfClasses: NamedNode[];\n rdfDefaultResourceHash: string;\n rdfFieldProperties: Record<string, NamedNode>;\n};\nexport type SchemaModel<T extends SchemaFields> = typeof Model & Constructor<z.infer<ZodObject<T>>>;\nexport type SchemaFields = Record<string, ZodType>;\n\nexport interface SchemaConfig<T extends SchemaFields> {\n crdts?: boolean;\n rdfContext?: string;\n rdfClass?: string;\n rdfDefaultResourceHash?: string;\n fields: T;\n relations?: Record<string, unknown>;\n}\n\nexport function defineSchema<T extends SchemaFields>(config: SchemaConfig<T>): SchemaModel<T> {\n const rdfContext = config.rdfContext ? { default: config.rdfContext } : { default: 'solid' };\n const { default: defaultPrefix, ...extraContext } = rdfContext;\n\n return class extends Model {\n\n public static schema = {\n fields: object(config.fields) as unknown as ZodObject,\n rdfContext,\n rdfDefaultResourceHash: config.rdfDefaultResourceHash ?? 'it',\n rdfClasses: config.rdfClass\n ? [\n new RDFNamedNode(\n expandIRI(config.rdfClass, {\n defaultPrefix,\n extraContext,\n }),\n ),\n ]\n : [],\n rdfFieldProperties: Object.fromEntries(\n Object.entries(config.fields).map(([field, definition]) => [\n field,\n new RDFNamedNode(\n expandIRI(definition.rdfProperty() ?? field, {\n defaultPrefix,\n extraContext,\n }),\n ),\n ]),\n ),\n };\n \n } as SchemaModel<T>;\n}\n","import { ZodDefault, ZodOptional } from 'zod';\nimport type { ZodType } from 'zod';\n\nfunction getDeepMeta<T extends keyof CustomMeta>(type: ZodType, key: T): CustomMeta[T] | undefined {\n const meta = type.meta();\n\n if (meta && key in meta) {\n return meta[key];\n }\n\n if (type instanceof ZodDefault) {\n return getDeepMeta(type.def.innerType as ZodType, key);\n }\n\n if (type instanceof ZodOptional) {\n return getDeepMeta(type.def.innerType as ZodType, key);\n }\n\n return undefined;\n}\n\nexport interface CustomMeta {\n rdfProperty?: string;\n}\n\nexport function rdfProperty<T extends ZodType>(this: T): string | undefined;\nexport function rdfProperty<T extends ZodType>(this: T, value: string): T;\nexport function rdfProperty<T extends ZodType>(this: T, value?: string): T | string | undefined {\n if (typeof value !== 'string') {\n return getDeepMeta(this, 'rdfProperty');\n }\n\n return this.meta({ rdfProperty: value });\n}\n\ndeclare module 'zod' {\n interface GlobalMeta extends CustomMeta {}\n}\n","// This folder is used to extend Zod's native functionality with Soukai features by patching the prototypes\n// of core classes. This may seem like an anti-pattern, but it's actually been recommended by the author of Zod.\n//\n// See https://github.com/colinhacks/zod/pull/3445#issuecomment-2091463120\n\nimport { z } from 'zod';\nimport * as coreExtensions from './extensions/core';\n\nexport type ZodCoreExtensions = typeof coreExtensions;\n\nexport function patchZod(): void {\n for (const [method, implementation] of Object.entries(coreExtensions)) {\n z.ZodType.prototype[method] = implementation;\n }\n}\n\ndeclare module 'zod' {\n interface ZodType extends ZodCoreExtensions {}\n}\n"],"names":["RDF_TYPE","expandIRI","getFinalType","type","ZodOptional","ZodDefault","castValue","value","finalType","ZodArray","item","ZodURL","RDFNamedNode","RDFLiteral","serializeToRDF","model","fields","rdfDefaultResourceHash","rdfClasses","rdfFieldProperties","subject","statements","rdfClass","RDFQuad","field","object","Model","MagicObject","attributes","__publicField","property","quadsToJsonLD","quadsToTurtle","defineSchema","config","rdfContext","defaultPrefix","extraContext","_a","definition","getDeepMeta","key","meta","rdfProperty","patchZod","method","implementation","coreExtensions","z"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "soukai-bis",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"exports": {
|
|
7
|
+
"types": "./dist/soukai-bis.d.ts",
|
|
8
|
+
"default": "./dist/soukai-bis.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Noel De Martin",
|
|
16
|
+
"repository": "github:NoelDeMartin/soukai",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "vite build",
|
|
19
|
+
"lint": "noeldemartin-lint src",
|
|
20
|
+
"test": "vitest --run",
|
|
21
|
+
"test:ci": "vitest --run --reporter verbose --retry=3",
|
|
22
|
+
"verify": "noeldemartin-verify"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@noeldemartin/solid-utils": "next",
|
|
26
|
+
"@noeldemartin/utils": "next",
|
|
27
|
+
"zod": "^4.3.4"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@arethetypeswrong/cli": "catalog:",
|
|
31
|
+
"@noeldemartin/scripts": "catalog:",
|
|
32
|
+
"@rdfjs/types": "catalog:",
|
|
33
|
+
"eslint": "catalog:",
|
|
34
|
+
"publint": "catalog:"
|
|
35
|
+
},
|
|
36
|
+
"eslintConfig": {
|
|
37
|
+
"extends": [
|
|
38
|
+
"@noeldemartin/eslint-config-typescript"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
2
|
+
import { ZodError } from 'zod';
|
|
3
|
+
|
|
4
|
+
import User from 'soukai-bis/testing/stubs/User';
|
|
5
|
+
|
|
6
|
+
describe('Model', () => {
|
|
7
|
+
|
|
8
|
+
it('creates instances', () => {
|
|
9
|
+
const user = new User({ name: 'John Doe' });
|
|
10
|
+
|
|
11
|
+
expect(user.name).toEqual('John Doe');
|
|
12
|
+
expectTypeOf(user).toExtend<{ name: string; email?: string; age?: number; friendUrls: string[] }>();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('validates attributes in constructor', () => {
|
|
16
|
+
expect(() => new User({ name: 'John Doe', email: 'invalid-email' })).toThrow(ZodError);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('serializes to JsonLD', async () => {
|
|
20
|
+
const user = new User({ name: 'John Doe', friendUrls: ['https://example.pod/alice#me'] });
|
|
21
|
+
|
|
22
|
+
expect(await user.toJsonLD()).toEqualJsonLD({
|
|
23
|
+
'@context': 'http://xmlns.com/foaf/0.1/',
|
|
24
|
+
'@id': '#it',
|
|
25
|
+
'@type': 'Person',
|
|
26
|
+
'name': 'John Doe',
|
|
27
|
+
'knows': [{ '@id': 'https://example.pod/alice#me' }],
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('serializes to Turtle', async () => {
|
|
32
|
+
const user = new User({ name: 'John Doe', friendUrls: ['https://example.pod/alice#me'] });
|
|
33
|
+
|
|
34
|
+
expect(await user.toTurtle()).toEqualTurtle(`
|
|
35
|
+
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
|
36
|
+
|
|
37
|
+
<#it>
|
|
38
|
+
a foaf:Person ;
|
|
39
|
+
foaf:name "John Doe" ;
|
|
40
|
+
foaf:knows <https://example.pod/alice#me> .
|
|
41
|
+
`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { MagicObject } from '@noeldemartin/utils';
|
|
2
|
+
import { quadsToJsonLD, quadsToTurtle } from '@noeldemartin/solid-utils';
|
|
3
|
+
import type { JsonLD } from '@noeldemartin/solid-utils';
|
|
4
|
+
|
|
5
|
+
import { serializeToRDF } from './concerns/serializes-to-rdf';
|
|
6
|
+
import type { Schema } from './schema';
|
|
7
|
+
|
|
8
|
+
export default class Model extends MagicObject {
|
|
9
|
+
|
|
10
|
+
public static schema: Schema;
|
|
11
|
+
|
|
12
|
+
public static<T extends typeof Model>(): T;
|
|
13
|
+
public static<T extends typeof Model, K extends keyof T>(property: K): T[K];
|
|
14
|
+
public static<T extends typeof Model, K extends keyof T>(property?: K): T | T[K] {
|
|
15
|
+
return super.static<T, K>(property as K);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare public url?: string;
|
|
19
|
+
private __attributes: Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
public constructor(attributes: Record<string, unknown> = {}) {
|
|
22
|
+
super();
|
|
23
|
+
|
|
24
|
+
if (this.static().isConjuring()) {
|
|
25
|
+
this.__attributes = {};
|
|
26
|
+
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.__attributes = this.static().schema.fields.parse(attributes);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public getAttributes(): Record<string, unknown> {
|
|
34
|
+
return this.__attributes;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async toJsonLD(): Promise<JsonLD> {
|
|
38
|
+
return quadsToJsonLD(serializeToRDF(this));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public async toTurtle(): Promise<string> {
|
|
42
|
+
return quadsToTurtle(serializeToRDF(this));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected __get(property: string): unknown {
|
|
46
|
+
return this.__attributes[property];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ZodArray, ZodDefault, ZodOptional, ZodURL } from 'zod';
|
|
2
|
+
import { RDFLiteral, RDFNamedNode, RDFQuad } from '@noeldemartin/solid-utils';
|
|
3
|
+
import type { Quad, Quad_Object } from '@rdfjs/types';
|
|
4
|
+
import type { SomeType } from 'zod/v4/core';
|
|
5
|
+
|
|
6
|
+
import { RDF_TYPE } from 'soukai-bis/models/constants';
|
|
7
|
+
import type Model from 'soukai-bis/models/Model';
|
|
8
|
+
|
|
9
|
+
function getFinalType(type: SomeType): SomeType {
|
|
10
|
+
if (type instanceof ZodOptional) {
|
|
11
|
+
return getFinalType(type.def.innerType);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (type instanceof ZodDefault) {
|
|
15
|
+
return getFinalType(type.def.innerType);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return type;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function castValue(value: unknown, type: SomeType): Quad_Object[] {
|
|
22
|
+
const finalType = getFinalType(type);
|
|
23
|
+
|
|
24
|
+
if (finalType instanceof ZodArray) {
|
|
25
|
+
const arrayValue = Array.isArray(value) ? value : value === null || value === undefined ? [] : [value];
|
|
26
|
+
|
|
27
|
+
return arrayValue.flatMap((item) => castValue(item, finalType.def.element));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (finalType instanceof ZodURL) {
|
|
31
|
+
return [new RDFNamedNode(String(value))];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return [new RDFLiteral(String(value))];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function serializeToRDF(model: Model): Quad[] {
|
|
38
|
+
const { fields, rdfDefaultResourceHash, rdfClasses, rdfFieldProperties } = model.static().schema;
|
|
39
|
+
const subject = new RDFNamedNode(model.url ?? `#${rdfDefaultResourceHash}`);
|
|
40
|
+
const statements: Quad[] = [];
|
|
41
|
+
|
|
42
|
+
for (const rdfClass of rdfClasses) {
|
|
43
|
+
statements.push(new RDFQuad(subject, RDF_TYPE, rdfClass));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const [field, value] of Object.entries(model.getAttributes())) {
|
|
47
|
+
for (const object of castValue(value, fields.def.shape[field])) {
|
|
48
|
+
statements.push(new RDFQuad(subject, rdfFieldProperties[field], object));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return statements;
|
|
53
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { object } from 'zod';
|
|
2
|
+
import { RDFNamedNode, expandIRI } from '@noeldemartin/solid-utils';
|
|
3
|
+
import type { Constructor } from '@noeldemartin/utils';
|
|
4
|
+
import type { ZodObject, ZodType, z } from 'zod';
|
|
5
|
+
import type { NamedNode } from '@rdfjs/types';
|
|
6
|
+
|
|
7
|
+
import Model from './Model';
|
|
8
|
+
|
|
9
|
+
export type Schema<T extends SchemaFields = SchemaFields> = {
|
|
10
|
+
fields: ZodObject<T>;
|
|
11
|
+
rdfContext: { default: string } & Record<string, string>;
|
|
12
|
+
rdfClasses: NamedNode[];
|
|
13
|
+
rdfDefaultResourceHash: string;
|
|
14
|
+
rdfFieldProperties: Record<string, NamedNode>;
|
|
15
|
+
};
|
|
16
|
+
export type SchemaModel<T extends SchemaFields> = typeof Model & Constructor<z.infer<ZodObject<T>>>;
|
|
17
|
+
export type SchemaFields = Record<string, ZodType>;
|
|
18
|
+
|
|
19
|
+
export interface SchemaConfig<T extends SchemaFields> {
|
|
20
|
+
crdts?: boolean;
|
|
21
|
+
rdfContext?: string;
|
|
22
|
+
rdfClass?: string;
|
|
23
|
+
rdfDefaultResourceHash?: string;
|
|
24
|
+
fields: T;
|
|
25
|
+
relations?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function defineSchema<T extends SchemaFields>(config: SchemaConfig<T>): SchemaModel<T> {
|
|
29
|
+
const rdfContext = config.rdfContext ? { default: config.rdfContext } : { default: 'solid' };
|
|
30
|
+
const { default: defaultPrefix, ...extraContext } = rdfContext;
|
|
31
|
+
|
|
32
|
+
return class extends Model {
|
|
33
|
+
|
|
34
|
+
public static schema = {
|
|
35
|
+
fields: object(config.fields) as unknown as ZodObject,
|
|
36
|
+
rdfContext,
|
|
37
|
+
rdfDefaultResourceHash: config.rdfDefaultResourceHash ?? 'it',
|
|
38
|
+
rdfClasses: config.rdfClass
|
|
39
|
+
? [
|
|
40
|
+
new RDFNamedNode(
|
|
41
|
+
expandIRI(config.rdfClass, {
|
|
42
|
+
defaultPrefix,
|
|
43
|
+
extraContext,
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
]
|
|
47
|
+
: [],
|
|
48
|
+
rdfFieldProperties: Object.fromEntries(
|
|
49
|
+
Object.entries(config.fields).map(([field, definition]) => [
|
|
50
|
+
field,
|
|
51
|
+
new RDFNamedNode(
|
|
52
|
+
expandIRI(definition.rdfProperty() ?? field, {
|
|
53
|
+
defaultPrefix,
|
|
54
|
+
extraContext,
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
]),
|
|
58
|
+
),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
} as SchemaModel<T>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineSchema } from 'soukai-bis/models';
|
|
2
|
+
import { array, email, number, string, url } from 'zod';
|
|
3
|
+
|
|
4
|
+
export default defineSchema({
|
|
5
|
+
crdts: true,
|
|
6
|
+
rdfContext: 'http://xmlns.com/foaf/0.1/',
|
|
7
|
+
rdfClass: 'Person',
|
|
8
|
+
fields: {
|
|
9
|
+
name: string(),
|
|
10
|
+
email: email().optional(),
|
|
11
|
+
age: number().optional(),
|
|
12
|
+
friendUrls: array(url()).rdfProperty('knows').default([]),
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ZodDefault, ZodOptional } from 'zod';
|
|
2
|
+
import type { ZodType } from 'zod';
|
|
3
|
+
|
|
4
|
+
function getDeepMeta<T extends keyof CustomMeta>(type: ZodType, key: T): CustomMeta[T] | undefined {
|
|
5
|
+
const meta = type.meta();
|
|
6
|
+
|
|
7
|
+
if (meta && key in meta) {
|
|
8
|
+
return meta[key];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (type instanceof ZodDefault) {
|
|
12
|
+
return getDeepMeta(type.def.innerType as ZodType, key);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (type instanceof ZodOptional) {
|
|
16
|
+
return getDeepMeta(type.def.innerType as ZodType, key);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CustomMeta {
|
|
23
|
+
rdfProperty?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function rdfProperty<T extends ZodType>(this: T): string | undefined;
|
|
27
|
+
export function rdfProperty<T extends ZodType>(this: T, value: string): T;
|
|
28
|
+
export function rdfProperty<T extends ZodType>(this: T, value?: string): T | string | undefined {
|
|
29
|
+
if (typeof value !== 'string') {
|
|
30
|
+
return getDeepMeta(this, 'rdfProperty');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return this.meta({ rdfProperty: value });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare module 'zod' {
|
|
37
|
+
interface GlobalMeta extends CustomMeta {}
|
|
38
|
+
}
|
package/src/zod/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// This folder is used to extend Zod's native functionality with Soukai features by patching the prototypes
|
|
2
|
+
// of core classes. This may seem like an anti-pattern, but it's actually been recommended by the author of Zod.
|
|
3
|
+
//
|
|
4
|
+
// See https://github.com/colinhacks/zod/pull/3445#issuecomment-2091463120
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import * as coreExtensions from './extensions/core';
|
|
8
|
+
|
|
9
|
+
export type ZodCoreExtensions = typeof coreExtensions;
|
|
10
|
+
|
|
11
|
+
export function patchZod(): void {
|
|
12
|
+
for (const [method, implementation] of Object.entries(coreExtensions)) {
|
|
13
|
+
z.ZodType.prototype[method] = implementation;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare module 'zod' {
|
|
18
|
+
interface ZodType extends ZodCoreExtensions {}
|
|
19
|
+
}
|