useChange... partially complete with incomplete test

main
Jackson Morgan 3 weeks ago
parent 69419a83b1
commit b69264a5f5
  1. 10
      .eslintrc
  2. 4
      .vscode/settings.json
  3. 1391
      package-lock.json
  4. 13
      packages/connected/test/mocks/MockConnectedLdoDataset.ts
  5. 45
      packages/connected/test/mocks/MockConnectedPlugin.ts
  6. 4
      packages/jsonld-dataset-proxy/src/setLanguagePreferences.ts
  7. 2
      packages/jsonld-dataset-proxy/src/util/createInteractOptions.ts
  8. 4
      packages/jsonld-dataset-proxy/src/write.ts
  9. 8
      packages/react/package.json
  10. 14
      packages/react/src/createLdoReactMethods.tsx
  11. 23
      packages/react/src/methods/change/types.ts
  12. 58
      packages/react/src/methods/change/useChangeDataset.ts
  13. 60
      packages/react/src/methods/change/useChangeMatchObject.ts
  14. 62
      packages/react/src/methods/change/useChangeMatchSubject.ts
  15. 85
      packages/react/src/methods/change/useChangeSubject.ts
  16. 17
      packages/react/src/methods/useDataset.ts
  17. 17
      packages/react/src/methods/useMatchObject.ts
  18. 17
      packages/react/src/methods/useMatchSubject.ts
  19. 24
      packages/react/src/methods/useSubject.ts
  20. 32
      packages/react/test/.ldo/post.context.ts
  21. 155
      packages/react/test/.ldo/post.schema.ts
  22. 19
      packages/react/test/.ldo/post.shapeTypes.ts
  23. 45
      packages/react/test/.ldo/post.typings.ts
  24. 459
      packages/react/test/.ldo/solidProfile.context.ts
  25. 749
      packages/react/test/.ldo/solidProfile.schema.ts
  26. 64
      packages/react/test/.ldo/solidProfile.shapeTypes.ts
  27. 293
      packages/react/test/.ldo/solidProfile.typings.ts
  28. 17
      packages/react/test/mockLdoMethods.ts
  29. 7
      packages/react/test/trivial.test.ts
  30. 227
      packages/react/test/useLdoForm.test.tsx
  31. 3
      packages/react/vitest.config.js

@ -1,6 +1,10 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "prettier", "react"],
"plugins": [
"@typescript-eslint",
"prettier",
"react"
],
"extends": [
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
@ -10,7 +14,9 @@
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": { "jsx": true }
"ecmaFeatures": {
"jsx": true
}
},
"settings": {
"react": {

@ -0,0 +1,4 @@
{
"typescript.preferences.importModuleSpecifierEnding": "js",
"javascript.preferences.importModuleSpecifierEnding": "js"
}

1391
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,13 @@
import { ConnectedLdoDataset } from "@ldo/connected";
import { createDatasetFactory } from "@ldo/dataset";
import { createTransactionDatasetFactory } from "@ldo/subscribable-dataset";
import { mockConnectedPlugin } from "./MockConnectedPlugin.js";
export function createMockConnectedLdoDataset() {
const nextGraphLdoDataset = new ConnectedLdoDataset(
[mockConnectedPlugin],
createDatasetFactory(),
createTransactionDatasetFactory(),
);
return nextGraphLdoDataset;
}

@ -0,0 +1,45 @@
import type { ConnectedContext, ConnectedPlugin } from "@ldo/connected";
import { MockResource } from "./MockResource.js";
import { v4 } from "uuid";
/**
* The Type of the SolidConnectedContext
*/
export interface MockConnectedContext {}
export interface MockConnectedPlugin
extends ConnectedPlugin<
"mock",
string,
MockResource,
MockConnectedContext,
undefined
> {
name: "mock";
getResource: (uri: string, context: ConnectedContext<this[]>) => MockResource;
createResource(context: ConnectedContext<this[]>): Promise<MockResource>;
}
export const mockConnectedPlugin: MockConnectedPlugin = {
name: "mock",
getResource: function (
uri: string,
_context: ConnectedContext<MockConnectedPlugin[]>,
): MockResource {
return new MockResource(uri);
},
createResource: async function (): Promise<MockResource> {
return new MockResource(v4());
},
isUriValid: function (uri: string): uri is string {
return typeof uri === "string";
},
initialContext: {},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore "Types" only exists for the typing system
types: {},
};

@ -1,6 +1,6 @@
import type { LanguageOrdering } from "./language/languageTypes.js";
import type { InteractOptions } from "./util/createInteractOptions.js";
import { createInteractOptions } from "./util/createInteractOptions.js";
import { createProxyInteractOptions } from "./util/createInteractOptions.js";
/**
* Set the default language pr
@ -10,5 +10,5 @@ import { createInteractOptions } from "./util/createInteractOptions.js";
export function setLanguagePreferences(
...languageOrdering: LanguageOrdering
): InteractOptions {
return createInteractOptions("languageOrdering", languageOrdering);
return createProxyInteractOptions("languageOrdering", languageOrdering);
}

@ -19,7 +19,7 @@ export interface InteractOptions {
usingCopy<T extends ObjectLike>(...objects: T[]): T[];
}
export function createInteractOptions(
export function createProxyInteractOptions(
paramKey: string,
parameter: unknown,
): InteractOptions {

@ -1,6 +1,6 @@
import type { GraphNode } from "@ldo/rdf-utils";
import type { InteractOptions } from "./util/createInteractOptions.js";
import { createInteractOptions } from "./util/createInteractOptions.js";
import { createProxyInteractOptions } from "./util/createInteractOptions.js";
/**
* Set the graphs that should be written to
@ -8,5 +8,5 @@ import { createInteractOptions } from "./util/createInteractOptions.js";
* @returns a write builder
*/
export function write(...graphs: GraphNode[]): InteractOptions {
return createInteractOptions("writeGraphs", graphs);
return createProxyInteractOptions("writeGraphs", graphs);
}

@ -36,9 +36,11 @@
"homepage": "https://github.com/o-development/ldo/tree/main/packages/solid-react#readme",
"devDependencies": {
"@rdfjs/types": "^1.0.1",
"@testing-library/react": "^14.1.2",
"start-server-and-test": "^2.0.3",
"ts-node": "^10.9.2"
"@testing-library/jest-dom": "^6.7.0",
"@testing-library/react": "^14.3.1",
"@testing-library/user-event": "^14.6.1",
"jsdom": "^26.1.0",
"vitest": "^3.2.4"
},
"dependencies": {
"@ldo/connected": "^1.0.0-alpha.30",

@ -10,6 +10,11 @@ import { createUseResource } from "./methods/useResource.js";
import { createUseSubject } from "./methods/useSubject.js";
import { createUseSubscribeToResource } from "./methods/useSubscribeToResource.js";
import { createUseLinkQuery } from "./methods/useLinkQuery.js";
import { createUseDataset } from "./methods/useDataset.js";
import { createUseChangeDataset } from "./methods/change/useChangeDataset.js";
import { createUseChangeSubject } from "./methods/change/useChangeSubject.js";
import { createUseChangeMatchObject } from "./methods/change/useChangeMatchObject.js";
import { createUseChangeMatchSubject } from "./methods/change/useChangeMatchSubject.js";
/**
* A function that creates all common react functions given specific plugin.
@ -31,6 +36,10 @@ import { createUseLinkQuery } from "./methods/useLinkQuery.js";
* useSubject,
* useSubscribeToResource,
* useLinkQuery,
* useChangeDataset,
* useChangeSubject,
* useChangeMatchObject,
* useChangeMatchSubject,
* } = createLdoReactMethods([
* solidConnectedPlugin,
* nextGraphConnectedPlugin
@ -66,6 +75,7 @@ export function createLdoReactMethods<
return {
dataset,
useDataset: createUseDataset(dataset),
useLdo: createUseLdo(dataset),
useMatchObject: createUseMatchObject(dataset),
useMatchSubject: createUseMatchSubject(dataset),
@ -73,5 +83,9 @@ export function createLdoReactMethods<
useSubject: createUseSubject(dataset),
useSubscribeToResource: createUseSubscribeToResource(dataset),
useLinkQuery: createUseLinkQuery(dataset),
useChangeDataset: createUseChangeDataset(dataset),
useChangeSubject: createUseChangeSubject(dataset),
useChangeMatchObject: createUseChangeMatchObject(dataset),
useChangeMatchSubject: createUseChangeMatchSubject(dataset),
};
}

@ -0,0 +1,23 @@
import type {
ConnectedLdoTransactionDataset,
ConnectedPlugin,
} from "@ldo/connected";
import type { LdoBase, LdSet } from "@ldo/ldo";
export type useChangeReturn<Type, Plugins extends ConnectedPlugin[]> = [
Type,
useChangeSetData<Type>,
useChangeCommitData<Plugins>,
];
type BaseOtherType = LdoBase | LdSet<LdoBase>;
export type useChangeSetData<T> = <
OtherType extends BaseOtherType | undefined = undefined,
>(
changer: (toChange: OtherType extends undefined ? T : OtherType) => void,
input?: OtherType,
) => void;
export type useChangeCommitData<Plugins extends ConnectedPlugin[]> =
() => ReturnType<ConnectedLdoTransactionDataset<Plugins>["commitToRemote"]>;

@ -0,0 +1,58 @@
import { useCallback, useMemo } from "react";
import type {
ConnectedLdoDataset,
ConnectedLdoTransactionDataset,
ConnectedPlugin,
IConnectedLdoDataset,
} from "@ldo/connected";
import type { useChangeCommitData } from "./types.js";
export type useChangeDatasetReturn<Plugins extends ConnectedPlugin[]> = [
ConnectedLdoTransactionDataset<Plugins>,
useChangeSetDataset<Plugins>,
useChangeCommitData<Plugins>,
];
export type useChangeSetDataset<Plugins extends ConnectedPlugin[]> = (
changer: (toChange: ConnectedLdoTransactionDataset<Plugins>) => void,
) => void;
/**
* @internal
*
* Creates a useChangeDataset function
*/
export function createUseChangeDataset<Plugins extends ConnectedPlugin[]>(
dataset: ConnectedLdoDataset<Plugins>,
) {
/**
* Returns a transaction on the dataset that can be modified and committed
*/
return function useChangeDataset(
specificDataset?: IConnectedLdoDataset<Plugins>,
): useChangeDatasetReturn<Plugins> {
const transactionDataset = useMemo(() => {
return (
specificDataset ?? dataset
).startTransaction() as ConnectedLdoTransactionDataset<Plugins>;
}, [specificDataset]);
const setData = useCallback<useChangeSetDataset<Plugins>>(
(changer) => {
const subTransaction = transactionDataset.startTransaction();
changer(subTransaction);
subTransaction.commit();
},
[transactionDataset],
);
const commitData = useCallback<useChangeCommitData<Plugins>>(() => {
return transactionDataset.commitToRemote();
}, [transactionDataset]);
return useMemo(
() => [transactionDataset, setData, commitData],
[transactionDataset, setData, commitData],
);
};
}

@ -0,0 +1,60 @@
import { useCallback, useMemo } from "react";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import { createUseChangeDataset } from "./useChangeDataset.js";
import type { LdoBase, LdSet, ShapeType } from "@ldo/ldo";
import type { QuadMatch } from "@ldo/rdf-utils";
import type { UseMatchObjectOptions } from "../useMatchObject.js";
import { createUseMatchObject } from "../useMatchObject.js";
import type { useChangeReturn, useChangeSetData } from "./types.js";
/**
* @internal
*
* Creates a useChangeMatchObject function
*/
export function createUseChangeMatchObject<Plugins extends ConnectedPlugin[]>(
dataset: ConnectedLdoDataset<Plugins>,
) {
const useChangeDataset = createUseChangeDataset(dataset);
const useMatchObject = createUseMatchObject(dataset);
/**
* Returns a list of matched objects that can be modified and committed
*/
return function useChangeSubject<Type extends LdoBase>(
shapeType: ShapeType<Type>,
writeResource: Plugins[number]["types"]["resource"],
subject?: QuadMatch[0] | string,
predicate?: QuadMatch[1] | string,
graph?: QuadMatch[3] | string,
options?: UseMatchObjectOptions<Plugins>,
): useChangeReturn<LdSet<Type>, Plugins> {
const [transactionDataset, setDataset, commitData] = useChangeDataset(
options?.dataset,
);
const ldObjects = useMatchObject(shapeType, subject, predicate, graph, {
dataset: transactionDataset,
});
const setData = useCallback<useChangeSetData<LdSet<Type>>>(
(changer) => {
setDataset((dataset) => {
const ldSet = dataset
.usingType(shapeType)
.write(writeResource.uri)
.matchObject(subject, predicate, graph);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
changer(ldSet);
});
},
[setDataset, subject, predicate, graph, shapeType, writeResource],
);
return useMemo(
() => [ldObjects, setData, commitData],
[ldObjects, setData, commitData],
);
};
}

@ -0,0 +1,62 @@
import { useCallback, useMemo } from "react";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import { createUseChangeDataset } from "./useChangeDataset.js";
import type { LdoBase, LdSet, ShapeType } from "@ldo/ldo";
import type { QuadMatch } from "@ldo/rdf-utils";
import type { useChangeReturn, useChangeSetData } from "./types.js";
import {
createUseMatchSubject,
type UseMatchSubjectOptions,
} from "../useMatchSubject.js";
/**
* @internal
*
* Creates a useChangeMatchSubject function
*/
export function createUseChangeMatchSubject<Plugins extends ConnectedPlugin[]>(
dataset: ConnectedLdoDataset<Plugins>,
) {
const useChangeDataset = createUseChangeDataset(dataset);
const useMatchSubject = createUseMatchSubject(dataset);
/**
* Returns a list of matched subjects that can be modified and committed
*/
return function useChangeSubject<Type extends LdoBase>(
shapeType: ShapeType<Type>,
writeResource: Plugins[number]["types"]["resource"],
predicate?: QuadMatch[1] | string,
object?: QuadMatch[2] | string,
graph?: QuadMatch[3] | string,
options?: UseMatchSubjectOptions<Plugins>,
): useChangeReturn<LdSet<Type>, Plugins> {
const [transactionDataset, setDataset, commitData] = useChangeDataset(
options?.dataset,
);
const ldSubjects = useMatchSubject(shapeType, predicate, object, graph, {
dataset: transactionDataset,
});
const setData = useCallback<useChangeSetData<LdSet<Type>>>(
(changer) => {
setDataset((dataset) => {
const ldSet = dataset
.usingType(shapeType)
.write(writeResource.uri)
.matchSubject(predicate, object, graph);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
changer(ldSet);
});
},
[setDataset, object, predicate, graph, shapeType, writeResource],
);
return useMemo(
() => [ldSubjects, setData, commitData],
[ldSubjects, setData, commitData],
);
};
}

@ -0,0 +1,85 @@
import { useCallback, useMemo } from "react";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import { createUseChangeDataset } from "./useChangeDataset.js";
import type { UseSubjectOptions } from "../useSubject.js";
import { createUseSubject } from "../useSubject.js";
import type { LdoBase, ShapeType } from "@ldo/ldo";
import type { SubjectNode } from "@ldo/rdf-utils";
import type { useChangeReturn, useChangeSetData } from "./types.js";
import { createProxyInteractOptions } from "@ldo/jsonld-dataset-proxy";
export type useChangeSubjectType<Plugins extends ConnectedPlugin[]> = {
<Type extends LdoBase>(
shapeType: ShapeType<Type>,
writeResource: Plugins[number]["types"]["resource"],
subject: string | SubjectNode,
options?: UseSubjectOptions<Plugins>,
): useChangeReturn<Type, Plugins>;
<Type extends LdoBase>(
shapeType: ShapeType<Type>,
writeResource: Plugins[number]["types"]["resource"],
subject?: string | SubjectNode,
options?: UseSubjectOptions<Plugins>,
): useChangeReturn<Type | undefined, Plugins>;
<Type extends LdoBase>(
shapeType: ShapeType<Type>,
writeResource: Plugins[number]["types"]["resource"],
subject?: string | SubjectNode,
options?: UseSubjectOptions<Plugins>,
): useChangeReturn<Type | undefined, Plugins>;
};
/**
* @internal
*
* Creates a useChangeSubject function
*/
export function createUseChangeSubject<Plugins extends ConnectedPlugin[]>(
dataset: ConnectedLdoDataset<Plugins>,
): useChangeSubjectType<Plugins> {
const useChangeDataset = createUseChangeDataset(dataset);
const useSubject = createUseSubject(dataset);
/**
* Returns a subject that can be modified and committed
*/
return function useChangeSubject<Type extends LdoBase>(
shapeType: ShapeType<Type>,
writeResource: Plugins[number]["types"]["resource"],
subject?: string | SubjectNode,
options?: UseSubjectOptions<Plugins>,
): useChangeReturn<Type | undefined, Plugins> {
const [transactionDataset, setDataset, commitData] = useChangeDataset(
options?.dataset,
);
const ldObject = useSubject(shapeType, subject, {
dataset: transactionDataset,
});
const setData = useCallback<useChangeSetData<Type>>(
(changer, otherType?) => {
if (!subject) return;
setDataset((dataset) => {
const ldObject = otherType
? createProxyInteractOptions("dataset", dataset).usingCopy(
otherType,
)
: dataset
.usingType(shapeType)
.write(writeResource.uri)
.fromSubject(subject);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
changer(ldObject);
});
},
[setDataset, subject, shapeType, writeResource],
);
return useMemo(
() => [ldObject, setData, commitData],
[ldObject, setData, commitData],
);
};
}

@ -0,0 +1,17 @@
import { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
/**
* @internal
*
* Creates a useDataset function
*/
export function createUseDataset<Plugins extends ConnectedPlugin[]>(
dataset: ConnectedLdoDataset<Plugins>,
) {
/**
* Returns the global dataset for the application
*/
return function useDataset() {
return dataset;
}
}

@ -3,7 +3,15 @@ import type { QuadMatch } from "@ldo/rdf-utils";
import type { LdoBuilder } from "@ldo/ldo";
import { useCallback } from "react";
import { useTrackingProxy } from "../util/useTrackingProxy.js";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import type {
ConnectedLdoDataset,
ConnectedPlugin,
IConnectedLdoDataset,
} from "@ldo/connected";
export interface UseMatchObjectOptions<Plugins extends ConnectedPlugin[]> {
dataset?: IConnectedLdoDataset<Plugins>;
}
/**
* @internal
@ -22,6 +30,7 @@ export function createUseMatchObject<Plugins extends ConnectedPlugin[]>(
subject?: QuadMatch[0] | string,
predicate?: QuadMatch[1] | string,
graph?: QuadMatch[3] | string,
options?: UseMatchObjectOptions<Plugins>,
): LdSet<Type> {
const matchObject = useCallback(
(builder: LdoBuilder<Type>) => {
@ -30,6 +39,10 @@ export function createUseMatchObject<Plugins extends ConnectedPlugin[]>(
[subject, predicate, graph],
);
return useTrackingProxy(shapeType, matchObject, dataset);
return useTrackingProxy(
shapeType,
matchObject,
options?.dataset ?? dataset,
);
};
}

@ -3,7 +3,15 @@ import type { QuadMatch } from "@ldo/rdf-utils";
import type { LdoBuilder } from "@ldo/ldo";
import { useCallback } from "react";
import { useTrackingProxy } from "../util/useTrackingProxy.js";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import type {
ConnectedLdoDataset,
ConnectedPlugin,
IConnectedLdoDataset,
} from "@ldo/connected";
export interface UseMatchSubjectOptions<Plugins extends ConnectedPlugin[]> {
dataset?: IConnectedLdoDataset<Plugins>;
}
/**
* @internal
@ -22,6 +30,7 @@ export function createUseMatchSubject<Plugins extends ConnectedPlugin[]>(
predicate?: QuadMatch[1] | string,
object?: QuadMatch[2] | string,
graph?: QuadMatch[3] | string,
options?: UseMatchSubjectOptions<Plugins>,
): LdSet<Type> {
const matchSubject = useCallback(
(builder: LdoBuilder<Type>) => {
@ -30,6 +39,10 @@ export function createUseMatchSubject<Plugins extends ConnectedPlugin[]>(
[predicate, object, graph],
);
return useTrackingProxy(shapeType, matchSubject, dataset);
return useTrackingProxy(
shapeType,
matchSubject,
options?.dataset ?? dataset,
);
};
}

@ -5,20 +5,31 @@ import type { LdoBase } from "@ldo/ldo";
import { useCallback } from "react";
import { useTrackingProxy } from "../util/useTrackingProxy.js";
import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected";
import type {
ConnectedLdoDataset,
ConnectedPlugin,
IConnectedLdoDataset,
} from "@ldo/connected";
export type useSubjectType = {
export interface UseSubjectOptions<Plugins extends ConnectedPlugin[]> {
dataset?: IConnectedLdoDataset<Plugins>;
}
export type useSubjectType<Plugins extends ConnectedPlugin[]> = {
<Type extends LdoBase>(
shapeType: ShapeType<Type>,
subject: string | SubjectNode,
options?: UseSubjectOptions<Plugins>,
): Type;
<Type extends LdoBase>(
shapeType: ShapeType<Type>,
subject?: string | SubjectNode,
options?: UseSubjectOptions<Plugins>,
): Type | undefined;
<Type extends LdoBase>(
shapeType: ShapeType<Type>,
subject?: string | SubjectNode,
options?: UseSubjectOptions<Plugins>,
): Type | undefined;
};
@ -29,7 +40,7 @@ export type useSubjectType = {
*/
export function createUseSubject<Plugins extends ConnectedPlugin[]>(
dataset: ConnectedLdoDataset<Plugins>,
): useSubjectType {
): useSubjectType<Plugins> {
/**
* Returns a Linked Data Object based on the provided subject. Triggers a
* rerender if the data is udpated.
@ -37,6 +48,7 @@ export function createUseSubject<Plugins extends ConnectedPlugin[]>(
return function useSubject<Type extends LdoBase>(
shapeType: ShapeType<Type>,
subject?: string | SubjectNode,
options?: UseSubjectOptions<Plugins>,
): Type | undefined {
const fromSubject = useCallback(
(builder: LdoBuilder<Type>) => {
@ -46,6 +58,10 @@ export function createUseSubject<Plugins extends ConnectedPlugin[]>(
[subject],
);
return useTrackingProxy(shapeType, fromSubject, dataset);
return useTrackingProxy(
shapeType,
fromSubject,
options?.dataset ?? dataset,
);
};
}

@ -0,0 +1,32 @@
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
* postContext: JSONLD Context for post
* =============================================================================
*/
export const postContext: ContextDefinition = {
type: {
"@id": "@type",
},
SocialMediaPosting: "http://schema.org/SocialMediaPosting",
CreativeWork: "http://schema.org/CreativeWork",
Thing: "http://schema.org/Thing",
articleBody: {
"@id": "http://schema.org/articleBody",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
uploadDate: {
"@id": "http://schema.org/uploadDate",
"@type": "http://www.w3.org/2001/XMLSchema#date",
},
image: {
"@id": "http://schema.org/image",
"@type": "@id",
},
publisher: {
"@id": "http://schema.org/publisher",
"@type": "@id",
"@container": "@set",
},
};

@ -0,0 +1,155 @@
import { Schema } from "shexj";
/**
* =============================================================================
* postSchema: ShexJ Schema for post
* =============================================================================
*/
export const postSchema: Schema = {
type: "Schema",
shapes: [
{
id: "https://example.com/PostSh",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: [
"http://schema.org/SocialMediaPosting",
"http://schema.org/CreativeWork",
"http://schema.org/Thing",
],
},
},
{
type: "TripleConstraint",
predicate: "http://schema.org/articleBody",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "articleBody",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The actual body of the article. ",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://schema.org/uploadDate",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#date",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "uploadDate",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"Date when this media object was uploaded to this site.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://schema.org/image",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "image",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A media object that encodes this CreativeWork. This property is a synonym for encoding.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://schema.org/publisher",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "publisher",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The publisher of the creative work.",
},
},
],
},
],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#label",
object: {
value: "SocialMediaPost",
},
},
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A post to a social media platform, including blog posts, tweets, Facebook posts, etc.",
},
},
],
},
},
],
};

@ -0,0 +1,19 @@
import { ShapeType } from "@ldo/ldo";
import { postSchema } from "./post.schema.js";
import { postContext } from "./post.context.js";
import { PostSh } from "./post.typings.js";
/**
* =============================================================================
* LDO ShapeTypes post
* =============================================================================
*/
/**
* PostSh ShapeType
*/
export const PostShShapeType: ShapeType<PostSh> = {
schema: postSchema,
shape: "https://example.com/PostSh",
context: postContext,
};

@ -0,0 +1,45 @@
import { LdSet, LdoJsonldContext } from "@ldo/ldo";
/**
* =============================================================================
* Typescript Typings for post
* =============================================================================
*/
/**
* PostSh Type
*/
export interface PostSh {
"@id"?: string;
"@context"?: LdoJsonldContext;
type:
| {
"@id": "SocialMediaPosting";
}
| {
"@id": "CreativeWork";
}
| {
"@id": "Thing";
};
/**
* The actual body of the article.
*/
articleBody?: string;
/**
* Date when this media object was uploaded to this site.
*/
uploadDate: string;
/**
* A media object that encodes this CreativeWork. This property is a synonym for encoding.
*/
image?: {
"@id": string;
};
/**
* The publisher of the creative work.
*/
publisher: LdSet<{
"@id": string;
}>;
}

@ -0,0 +1,459 @@
import { LdoJsonldContext } from "@ldo/ldo";
/**
* =============================================================================
* solidProfileContext: JSONLD Context for solidProfile
* =============================================================================
*/
export const solidProfileContext: LdoJsonldContext = {
type: {
"@id": "@type",
},
Person: {
"@id": "http://schema.org/Person",
"@context": {
type: {
"@id": "@type",
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
name: {
"@id": "http://xmlns.com/foaf/0.1/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasAddress: {
"@id": "http://www.w3.org/2006/vcard/ns#hasAddress",
"@type": "@id",
"@isCollection": true,
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "@id",
"@isCollection": true,
},
hasPhoto: {
"@id": "http://www.w3.org/2006/vcard/ns#hasPhoto",
"@type": "@id",
},
img: {
"@id": "http://xmlns.com/foaf/0.1/img",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasTelephone: {
"@id": "http://www.w3.org/2006/vcard/ns#hasTelephone",
"@type": "@id",
"@isCollection": true,
},
phone: {
"@id": "http://www.w3.org/2006/vcard/ns#phone",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
organizationName: {
"@id": "http://www.w3.org/2006/vcard/ns#organization-name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
role: {
"@id": "http://www.w3.org/2006/vcard/ns#role",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
trustedApp: {
"@id": "http://www.w3.org/ns/auth/acl#trustedApp",
"@type": "@id",
"@isCollection": true,
},
key: {
"@id": "http://www.w3.org/ns/auth/cert#key",
"@type": "@id",
"@isCollection": true,
},
inbox: {
"@id": "http://www.w3.org/ns/ldp#inbox",
"@type": "@id",
},
preferencesFile: {
"@id": "http://www.w3.org/ns/pim/space#preferencesFile",
"@type": "@id",
},
storage: {
"@id": "http://www.w3.org/ns/pim/space#storage",
"@type": "@id",
"@isCollection": true,
},
account: {
"@id": "http://www.w3.org/ns/solid/terms#account",
"@type": "@id",
},
privateTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#privateTypeIndex",
"@type": "@id",
"@isCollection": true,
},
publicTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#publicTypeIndex",
"@type": "@id",
"@isCollection": true,
},
knows: {
"@id": "http://xmlns.com/foaf/0.1/knows",
"@type": "@id",
"@isCollection": true,
},
},
},
Person2: {
"@id": "http://xmlns.com/foaf/0.1/Person",
"@context": {
type: {
"@id": "@type",
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
name: {
"@id": "http://xmlns.com/foaf/0.1/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasAddress: {
"@id": "http://www.w3.org/2006/vcard/ns#hasAddress",
"@type": "@id",
"@isCollection": true,
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "@id",
"@isCollection": true,
},
hasPhoto: {
"@id": "http://www.w3.org/2006/vcard/ns#hasPhoto",
"@type": "@id",
},
img: {
"@id": "http://xmlns.com/foaf/0.1/img",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasTelephone: {
"@id": "http://www.w3.org/2006/vcard/ns#hasTelephone",
"@type": "@id",
"@isCollection": true,
},
phone: {
"@id": "http://www.w3.org/2006/vcard/ns#phone",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
organizationName: {
"@id": "http://www.w3.org/2006/vcard/ns#organization-name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
role: {
"@id": "http://www.w3.org/2006/vcard/ns#role",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
trustedApp: {
"@id": "http://www.w3.org/ns/auth/acl#trustedApp",
"@type": "@id",
"@isCollection": true,
},
key: {
"@id": "http://www.w3.org/ns/auth/cert#key",
"@type": "@id",
"@isCollection": true,
},
inbox: {
"@id": "http://www.w3.org/ns/ldp#inbox",
"@type": "@id",
},
preferencesFile: {
"@id": "http://www.w3.org/ns/pim/space#preferencesFile",
"@type": "@id",
},
storage: {
"@id": "http://www.w3.org/ns/pim/space#storage",
"@type": "@id",
"@isCollection": true,
},
account: {
"@id": "http://www.w3.org/ns/solid/terms#account",
"@type": "@id",
},
privateTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#privateTypeIndex",
"@type": "@id",
"@isCollection": true,
},
publicTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#publicTypeIndex",
"@type": "@id",
"@isCollection": true,
},
knows: {
"@id": "http://xmlns.com/foaf/0.1/knows",
"@type": "@id",
"@isCollection": true,
},
},
},
fn: {
"@id": "http://www.w3.org/2006/vcard/ns#fn",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
name: {
"@id": "http://xmlns.com/foaf/0.1/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasAddress: {
"@id": "http://www.w3.org/2006/vcard/ns#hasAddress",
"@type": "@id",
"@isCollection": true,
},
countryName: {
"@id": "http://www.w3.org/2006/vcard/ns#country-name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
locality: {
"@id": "http://www.w3.org/2006/vcard/ns#locality",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
postalCode: {
"@id": "http://www.w3.org/2006/vcard/ns#postal-code",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
region: {
"@id": "http://www.w3.org/2006/vcard/ns#region",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
streetAddress: {
"@id": "http://www.w3.org/2006/vcard/ns#street-address",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "@id",
"@isCollection": true,
},
Dom: {
"@id": "http://www.w3.org/2006/vcard/ns#Dom",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Home: {
"@id": "http://www.w3.org/2006/vcard/ns#Home",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
ISDN: {
"@id": "http://www.w3.org/2006/vcard/ns#ISDN",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Internet: {
"@id": "http://www.w3.org/2006/vcard/ns#Internet",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Intl: {
"@id": "http://www.w3.org/2006/vcard/ns#Intl",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Label: {
"@id": "http://www.w3.org/2006/vcard/ns#Label",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Parcel: {
"@id": "http://www.w3.org/2006/vcard/ns#Parcel",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Postal: {
"@id": "http://www.w3.org/2006/vcard/ns#Postal",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Pref: {
"@id": "http://www.w3.org/2006/vcard/ns#Pref",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Work: {
"@id": "http://www.w3.org/2006/vcard/ns#Work",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
X400: {
"@id": "http://www.w3.org/2006/vcard/ns#X400",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
hasPhoto: {
"@id": "http://www.w3.org/2006/vcard/ns#hasPhoto",
"@type": "@id",
},
img: {
"@id": "http://xmlns.com/foaf/0.1/img",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
hasTelephone: {
"@id": "http://www.w3.org/2006/vcard/ns#hasTelephone",
"@type": "@id",
"@isCollection": true,
},
phone: {
"@id": "http://www.w3.org/2006/vcard/ns#phone",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
organizationName: {
"@id": "http://www.w3.org/2006/vcard/ns#organization-name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
role: {
"@id": "http://www.w3.org/2006/vcard/ns#role",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
trustedApp: {
"@id": "http://www.w3.org/ns/auth/acl#trustedApp",
"@type": "@id",
"@isCollection": true,
},
mode: {
"@id": "http://www.w3.org/ns/auth/acl#mode",
"@isCollection": true,
},
Append: "http://www.w3.org/ns/auth/acl#Append",
Control: "http://www.w3.org/ns/auth/acl#Control",
Read: "http://www.w3.org/ns/auth/acl#Read",
Write: "http://www.w3.org/ns/auth/acl#Write",
origin: {
"@id": "http://www.w3.org/ns/auth/acl#origin",
"@type": "@id",
},
key: {
"@id": "http://www.w3.org/ns/auth/cert#key",
"@type": "@id",
"@isCollection": true,
},
modulus: {
"@id": "http://www.w3.org/ns/auth/cert#modulus",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
exponent: {
"@id": "http://www.w3.org/ns/auth/cert#exponent",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
inbox: {
"@id": "http://www.w3.org/ns/ldp#inbox",
"@type": "@id",
},
preferencesFile: {
"@id": "http://www.w3.org/ns/pim/space#preferencesFile",
"@type": "@id",
},
storage: {
"@id": "http://www.w3.org/ns/pim/space#storage",
"@type": "@id",
"@isCollection": true,
},
account: {
"@id": "http://www.w3.org/ns/solid/terms#account",
"@type": "@id",
},
privateTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#privateTypeIndex",
"@type": "@id",
"@isCollection": true,
},
publicTypeIndex: {
"@id": "http://www.w3.org/ns/solid/terms#publicTypeIndex",
"@type": "@id",
"@isCollection": true,
},
knows: {
"@id": "http://xmlns.com/foaf/0.1/knows",
"@type": "@id",
"@isCollection": true,
},
};

@ -0,0 +1,749 @@
import { Schema } from "shexj";
/**
* =============================================================================
* solidProfileSchema: ShexJ Schema for solidProfile
* =============================================================================
*/
export const solidProfileSchema: Schema = {
type: "Schema",
shapes: [
{
id: "https://shaperepo.com/schemas/solidProfile#SolidProfileShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://schema.org/Person"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a Person (from Schema.org)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: ["http://xmlns.com/foaf/0.1/Person"],
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Defines the node as a Person (from foaf)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#fn",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The formatted name of a person. Example: John Smith",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://xmlns.com/foaf/0.1/name",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "An alternate way to define a person's name.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasAddress",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#AddressShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The person's street address.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasEmail",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#EmailShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The person's email.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasPhoto",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "A link to the person's photo",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://xmlns.com/foaf/0.1/img",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Photo link but in string form",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#hasTelephone",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#PhoneNumberShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "Person's telephone number",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#phone",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"An alternative way to define a person's telephone number using a string",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#organization-name",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The name of the organization with which the person is affiliated",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#role",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The name of the person's role in their organization",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#trustedApp",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#TrustedAppShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A list of app origins that are trusted by this user",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/cert#key",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#RSAPublicKeyShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A list of RSA public keys that are associated with private keys the user holds.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/ldp#inbox",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The user's LDP inbox to which apps can post notifications",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/pim/space#preferencesFile",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The user's preferences",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/pim/space#storage",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The location of a Solid storage server related to this WebId",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#account",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The user's account",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#privateTypeIndex",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A registry of all types used on the user's Pod (for private access only)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/solid/terms#publicTypeIndex",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A registry of all types used on the user's Pod (for public access)",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://xmlns.com/foaf/0.1/knows",
valueExpr:
"https://shaperepo.com/schemas/solidProfile#SolidProfileShape",
min: 0,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"A list of WebIds for all the people this user knows.",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#AddressShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#country-name",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The name of the user's country of residence",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#locality",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The name of the user's locality (City, Town etc.) of residence",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#postal-code",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The user's postal code",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#region",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The name of the user's region (State, Province etc.) of residence",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#street-address",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The user's street address",
},
},
],
},
],
},
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#EmailShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/2006/vcard/ns#Dom",
"http://www.w3.org/2006/vcard/ns#Home",
"http://www.w3.org/2006/vcard/ns#ISDN",
"http://www.w3.org/2006/vcard/ns#Internet",
"http://www.w3.org/2006/vcard/ns#Intl",
"http://www.w3.org/2006/vcard/ns#Label",
"http://www.w3.org/2006/vcard/ns#Parcel",
"http://www.w3.org/2006/vcard/ns#Postal",
"http://www.w3.org/2006/vcard/ns#Pref",
"http://www.w3.org/2006/vcard/ns#Work",
"http://www.w3.org/2006/vcard/ns#X400",
],
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The type of email.",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#value",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The value of an email as a mailto link (Example <mailto:jane@example.com>)",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#PhoneNumberShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/2006/vcard/ns#Dom",
"http://www.w3.org/2006/vcard/ns#Home",
"http://www.w3.org/2006/vcard/ns#ISDN",
"http://www.w3.org/2006/vcard/ns#Internet",
"http://www.w3.org/2006/vcard/ns#Intl",
"http://www.w3.org/2006/vcard/ns#Label",
"http://www.w3.org/2006/vcard/ns#Parcel",
"http://www.w3.org/2006/vcard/ns#Postal",
"http://www.w3.org/2006/vcard/ns#Pref",
"http://www.w3.org/2006/vcard/ns#Work",
"http://www.w3.org/2006/vcard/ns#X400",
],
},
min: 0,
max: 1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "They type of Phone Number",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/2006/vcard/ns#value",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value:
"The value of a phone number as a tel link (Example <tel:555-555-5555>)",
},
},
],
},
],
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#TrustedAppShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#mode",
valueExpr: {
type: "NodeConstraint",
values: [
"http://www.w3.org/ns/auth/acl#Append",
"http://www.w3.org/ns/auth/acl#Control",
"http://www.w3.org/ns/auth/acl#Read",
"http://www.w3.org/ns/auth/acl#Write",
],
},
min: 1,
max: -1,
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The level of access provided to this origin",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/acl#origin",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "The app origin the user trusts",
},
},
],
},
],
},
},
},
{
id: "https://shaperepo.com/schemas/solidProfile#RSAPublicKeyShape",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
type: "EachOf",
expressions: [
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/cert#modulus",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#string",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "RSA Modulus",
},
},
],
},
{
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/auth/cert#exponent",
valueExpr: {
type: "NodeConstraint",
datatype: "http://www.w3.org/2001/XMLSchema#integer",
},
annotations: [
{
type: "Annotation",
predicate: "http://www.w3.org/2000/01/rdf-schema#comment",
object: {
value: "RSA Exponent",
},
},
],
},
],
},
},
},
],
};

@ -0,0 +1,64 @@
import { ShapeType } from "@ldo/ldo";
import { solidProfileSchema } from "./solidProfile.schema.js";
import { solidProfileContext } from "./solidProfile.context.js";
import { AddressShape, EmailShape, PhoneNumberShape, RSAPublicKeyShape, SolidProfileShape, TrustedAppShape } from "./solidProfile.typings.js";
/**
* =============================================================================
* LDO ShapeTypes solidProfile
* =============================================================================
*/
/**
* SolidProfileShape ShapeType
*/
export const SolidProfileShapeShapeType: ShapeType<SolidProfileShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#SolidProfileShape",
context: solidProfileContext,
};
/**
* AddressShape ShapeType
*/
export const AddressShapeShapeType: ShapeType<AddressShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#AddressShape",
context: solidProfileContext,
};
/**
* EmailShape ShapeType
*/
export const EmailShapeShapeType: ShapeType<EmailShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#EmailShape",
context: solidProfileContext,
};
/**
* PhoneNumberShape ShapeType
*/
export const PhoneNumberShapeShapeType: ShapeType<PhoneNumberShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#PhoneNumberShape",
context: solidProfileContext,
};
/**
* TrustedAppShape ShapeType
*/
export const TrustedAppShapeShapeType: ShapeType<TrustedAppShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#TrustedAppShape",
context: solidProfileContext,
};
/**
* RSAPublicKeyShape ShapeType
*/
export const RSAPublicKeyShapeShapeType: ShapeType<RSAPublicKeyShape> = {
schema: solidProfileSchema,
shape: "https://shaperepo.com/schemas/solidProfile#RSAPublicKeyShape",
context: solidProfileContext,
};

@ -0,0 +1,293 @@
import { LdoJsonldContext, LdSet } from "@ldo/ldo";
/**
* =============================================================================
* Typescript Typings for solidProfile
* =============================================================================
*/
/**
* SolidProfileShape Type
*/
export interface SolidProfileShape {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* Defines the node as a Person (from Schema.org) | Defines the node as a Person (from foaf)
*/
type: LdSet<
| {
"@id": "Person";
}
| {
"@id": "Person2";
}
>;
/**
* The formatted name of a person. Example: John Smith
*/
fn?: string;
/**
* An alternate way to define a person's name.
*/
name?: string;
/**
* The person's street address.
*/
hasAddress?: LdSet<AddressShape>;
/**
* The person's email.
*/
hasEmail?: LdSet<EmailShape>;
/**
* A link to the person's photo
*/
hasPhoto?: {
"@id": string;
};
/**
* Photo link but in string form
*/
img?: string;
/**
* Person's telephone number
*/
hasTelephone?: LdSet<PhoneNumberShape>;
/**
* An alternative way to define a person's telephone number using a string
*/
phone?: string;
/**
* The name of the organization with which the person is affiliated
*/
organizationName?: string;
/**
* The name of the person's role in their organization
*/
role?: string;
/**
* A list of app origins that are trusted by this user
*/
trustedApp?: LdSet<TrustedAppShape>;
/**
* A list of RSA public keys that are associated with private keys the user holds.
*/
key?: LdSet<RSAPublicKeyShape>;
/**
* The user's LDP inbox to which apps can post notifications
*/
inbox: {
"@id": string;
};
/**
* The user's preferences
*/
preferencesFile?: {
"@id": string;
};
/**
* The location of a Solid storage server related to this WebId
*/
storage?: LdSet<{
"@id": string;
}>;
/**
* The user's account
*/
account?: {
"@id": string;
};
/**
* A registry of all types used on the user's Pod (for private access only)
*/
privateTypeIndex?: LdSet<{
"@id": string;
}>;
/**
* A registry of all types used on the user's Pod (for public access)
*/
publicTypeIndex?: LdSet<{
"@id": string;
}>;
/**
* A list of WebIds for all the people this user knows.
*/
knows?: LdSet<SolidProfileShape>;
}
/**
* AddressShape Type
*/
export interface AddressShape {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* The name of the user's country of residence
*/
countryName?: string;
/**
* The name of the user's locality (City, Town etc.) of residence
*/
locality?: string;
/**
* The user's postal code
*/
postalCode?: string;
/**
* The name of the user's region (State, Province etc.) of residence
*/
region?: string;
/**
* The user's street address
*/
streetAddress?: string;
}
/**
* EmailShape Type
*/
export interface EmailShape {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* The type of email.
*/
type?:
| {
"@id": "Dom";
}
| {
"@id": "Home";
}
| {
"@id": "ISDN";
}
| {
"@id": "Internet";
}
| {
"@id": "Intl";
}
| {
"@id": "Label";
}
| {
"@id": "Parcel";
}
| {
"@id": "Postal";
}
| {
"@id": "Pref";
}
| {
"@id": "Work";
}
| {
"@id": "X400";
};
/**
* The value of an email as a mailto link (Example <mailto:jane@example.com>)
*/
value: {
"@id": string;
};
}
/**
* PhoneNumberShape Type
*/
export interface PhoneNumberShape {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* They type of Phone Number
*/
type?:
| {
"@id": "Dom";
}
| {
"@id": "Home";
}
| {
"@id": "ISDN";
}
| {
"@id": "Internet";
}
| {
"@id": "Intl";
}
| {
"@id": "Label";
}
| {
"@id": "Parcel";
}
| {
"@id": "Postal";
}
| {
"@id": "Pref";
}
| {
"@id": "Work";
}
| {
"@id": "X400";
};
/**
* The value of a phone number as a tel link (Example <tel:555-555-5555>)
*/
value: {
"@id": string;
};
}
/**
* TrustedAppShape Type
*/
export interface TrustedAppShape {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* The level of access provided to this origin
*/
mode: LdSet<
| {
"@id": "Append";
}
| {
"@id": "Control";
}
| {
"@id": "Read";
}
| {
"@id": "Write";
}
>;
/**
* The app origin the user trusts
*/
origin: {
"@id": string;
};
}
/**
* RSAPublicKeyShape Type
*/
export interface RSAPublicKeyShape {
"@id"?: string;
"@context"?: LdoJsonldContext;
/**
* RSA Modulus
*/
modulus: string;
/**
* RSA Exponent
*/
exponent: number;
}

@ -0,0 +1,17 @@
import { createLdoReactMethods } from "../src/createLdoReactMethods.js";
import { mockConnectedPlugin } from "../../connected/test/mocks/MockConnectedPlugin.js";
export const {
dataset,
useLdo,
useMatchObject,
useMatchSubject,
useResource,
useSubject,
useSubscribeToResource,
useLinkQuery,
useChangeDataset,
useChangeSubject,
useChangeMatchSubject,
useChangeMatchObject,
} = createLdoReactMethods([mockConnectedPlugin]);

@ -1,7 +0,0 @@
import { describe, it, expect } from "vitest";
describe("react", () => {
it("trivial", () => {
expect(true).toBe(true);
});
});

@ -0,0 +1,227 @@
// useChangeSubject.spec.tsx
/// <reference types="@testing-library/jest-dom" />
import { useResource, useChangeSubject, useSubject } from "./mockLdoMethods.js";
import type { FunctionComponent } from "react";
import React from "react";
import { describe, it, expect, beforeEach } from "vitest";
import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { SolidProfileShapeShapeType } from "./.ldo/solidProfile.shapeTypes.js";
import { set } from "@ldo/jsonld-dataset-proxy";
import "@testing-library/jest-dom/vitest";
/**
* Test component: deterministic friend IDs (Example1, Example2, ...),
* accessible labels, and committed data shown via useSubject.
*/
const FormTest: FunctionComponent = () => {
const randomResource = useResource("random");
const submittedData = useSubject(SolidProfileShapeShapeType, "Example0");
const [data, setData, commitData] = useChangeSubject(
SolidProfileShapeShapeType,
randomResource,
"Example0",
);
return (
<div>
<h1>Form</h1>
<form
onSubmit={async (e) => {
e.preventDefault();
const result = await commitData();
console.log(result);
}}
>
{/* Primary name field */}
<input
aria-label="Name"
value={data?.fn ?? ""}
onChange={(e) =>
setData((profile) => {
profile.fn = e.target.value;
})
}
/>
{/* Friends */}
{data?.knows?.map((person) => (
<div key={person["@id"]} data-testid={`friend-${person["@id"]}`}>
<p>{person["@id"]}</p>
<input
aria-label={`Friend name for ${person["@id"]}`}
value={person?.fn ?? ""}
onChange={(e) =>
setData((p) => {
p.fn = e.target.value;
}, person)
}
/>
<button
type="button"
onClick={() => {
setData((cData) => {
cData.knows?.delete(person);
});
}}
>
Remove Friend
</button>
</div>
))}
<button
type="button"
onClick={() => {
// Auto-generate deterministic IDs: Example1, Example2, ...
const count = data?.knows?.size ?? 0;
const friendId = `Example${count + 1}`;
setData((cData) => {
cData.knows?.add({
"@id": friendId,
type: set({ "@id": "Person" }),
inbox: { "@id": "someInbox" },
});
});
}}
>
Add Friend
</button>
<button type="submit">Submit</button>
</form>
<hr />
{/* Committed view */}
<h1>Submitted Data</h1>
<section aria-label="Submitted Data">
<p data-testid="submitted-name">Name: {submittedData?.fn ?? ""}</p>
<ul data-testid="submitted-list">
{submittedData?.knows?.map((person) => (
<li key={person["@id"]}>
Id: {person["@id"]} Name: {person.fn}
</li>
))}
</ul>
</section>
</div>
);
};
/**
* Spec: drives the UI as requested.
*/
describe("useChangeSubject", () => {
let user: ReturnType<typeof userEvent.setup>;
beforeEach(() => {
user = userEvent.setup();
});
it("handles typing, list add/remove, and commit cycles", async () => {
render(<FormTest />);
// 1) Type "Example0" into the Name field, assert after each keystroke
const nameInput = screen.getByRole("textbox", { name: "Name" });
const targetName = "Example0";
let progressive = "";
for (const c of targetName) {
progressive += c;
await user.type(nameInput, c);
expect(nameInput).toHaveValue(progressive);
}
// Submitted is blank so far
const submittedSection = screen.getByRole("region", {
name: /submitted data/i,
});
const submittedName =
within(submittedSection).getByTestId("submitted-name");
const submittedList =
within(submittedSection).getByTestId("submitted-list");
expect(submittedName).toHaveTextContent("Name:");
expect(within(submittedList).queryAllByRole("listitem")).toHaveLength(0);
// 2) Add two friends -> Example1, Example2
const addFriendBtn = screen.getByRole("button", { name: /add friend/i });
await user.click(addFriendBtn);
await user.click(addFriendBtn);
const friend1 = screen.getByTestId("friend-Example1");
const friend2 = screen.getByTestId("friend-Example2");
expect(within(friend1).getByText("Example1")).toBeInTheDocument();
expect(within(friend2).getByText("Example2")).toBeInTheDocument();
// 3) Type friend names with per-keystroke assertions
const friend1Input = within(friend1).getByRole("textbox", {
name: "Friend name for Example1",
});
const friend2Input = within(friend2).getByRole("textbox", {
name: "Friend name for Example2",
});
const friend1Name = "Example1";
const friend2Name = "Example2";
let p = "";
for (const c of friend1Name) {
p += c;
await user.type(friend1Input, c);
expect(friend1Input).toHaveValue(p);
}
p = "";
for (const c of friend2Name) {
p += c;
await user.type(friend2Input, c);
expect(friend2Input).toHaveValue(p);
}
// Still nothing committed
expect(submittedName).toHaveTextContent("Name:");
expect(within(submittedList).queryAllByRole("listitem")).toHaveLength(0);
// 4) Remove Example2
await user.click(
within(friend2).getByRole("button", { name: /remove friend/i }),
);
expect(screen.queryByTestId("friend-Example2")).not.toBeInTheDocument();
// 5) Submit -> committed data reflects Example0 + Example1 only
await user.click(screen.getByRole("button", { name: /submit/i }));
// Form retained its values
expect(nameInput).toHaveValue("Example0");
expect(friend1Input).toHaveValue("Example1");
expect(screen.queryByTestId("friend-Example2")).not.toBeInTheDocument();
// Committed view updated
expect(submittedName).toHaveTextContent("Name: Example0");
const itemsAfterSubmit = within(submittedList).getAllByRole("listitem");
expect(itemsAfterSubmit).toHaveLength(1);
expect(itemsAfterSubmit[0]).toHaveTextContent(
"Id: Example1 Name: Example1",
);
// 6) Change name and resubmit
await user.clear(nameInput);
const newName = "anotherExample0";
let q = "";
for (const c of newName) {
q += c;
await user.type(nameInput, c);
expect(nameInput).toHaveValue(q);
}
await user.click(screen.getByRole("button", { name: /submit/i }));
expect(submittedName).toHaveTextContent(`Name: ${newName}`);
const itemsAfterSecondSubmit =
within(submittedList).getAllByRole("listitem");
expect(itemsAfterSecondSubmit).toHaveLength(1);
expect(itemsAfterSecondSubmit[0]).toHaveTextContent(
"Id: Example1 Name: Example1",
);
});
});

@ -1,9 +1,12 @@
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
coverage: {
provider: "istanbul",
},
environment: "jsdom",
},
});

Loading…
Cancel
Save