From d29a31346c173926852690bb85095a9ea7f2d613 Mon Sep 17 00:00:00 2001 From: Polle Pas Date: Thu, 7 Nov 2024 12:06:24 +0100 Subject: [PATCH] Fix spelling, add comments and fix .props not returning props that are not in the resources classdefs #700 --- browser/cli/src/generatePropTypeMapping.ts | 9 +- .../src/lib/atomic/getCurrentResource.ts | 3 +- .../src/lib/stores/appstate.svelte.ts | 4 - .../sveltekit-site/src/routes/+layout.svelte | 1 + browser/lib/src/ontologies/core.ts | 2 +- browser/lib/src/ontologies/server.ts | 125 +++++++++--------- browser/lib/src/ontology.ts | 6 +- browser/lib/src/resource.ts | 20 ++- .../src/lib/stores/getResource.svelte.ts | 7 +- docs/src/js-lib/resource.md | 7 +- docs/src/js-lib/store.md | 2 +- docs/src/svelte.md | 8 +- lib/defaults/ontologies.json | 10 +- 13 files changed, 118 insertions(+), 86 deletions(-) diff --git a/browser/cli/src/generatePropTypeMapping.ts b/browser/cli/src/generatePropTypeMapping.ts index 529332a2..8419d9d7 100644 --- a/browser/cli/src/generatePropTypeMapping.ts +++ b/browser/cli/src/generatePropTypeMapping.ts @@ -23,5 +23,12 @@ const generateLine = (subject: string, reverseMapping: ReverseMapping) => { const resource = store.getResourceLoading(subject); const datatype = resource.props.datatype as Datatype; - return `[${reverseMapping[subject]}]: ${DatatypeToTSTypeMap[datatype]}`; + const type = DatatypeToTSTypeMap[datatype]; + + if (!type) { + console.error(`Unknown datatype ${datatype} on property ${resource.title}`); + process.exit(1); + } + + return `[${reverseMapping[subject]}]: ${type}`; }; diff --git a/browser/create-template/templates/sveltekit-site/src/lib/atomic/getCurrentResource.ts b/browser/create-template/templates/sveltekit-site/src/lib/atomic/getCurrentResource.ts index 7ca9ba66..2f0aea62 100644 --- a/browser/create-template/templates/sveltekit-site/src/lib/atomic/getCurrentResource.ts +++ b/browser/create-template/templates/sveltekit-site/src/lib/atomic/getCurrentResource.ts @@ -15,7 +15,8 @@ export async function getCurrentResource( url: URL ): Promise { const store = getStore(); - // Svelte uses a special fetch function that inlines responses during server-side rendering. To make sure the store can make use of this we need to inject the fetch function into the store. + // Svelte uses a special fetch function that inlines responses during server-side rendering. + // To make sure the store can make use of this we need to inject the fetch function into the store. store.injectFetch(fetchOverride); const path = url.pathname; diff --git a/browser/create-template/templates/sveltekit-site/src/lib/stores/appstate.svelte.ts b/browser/create-template/templates/sveltekit-site/src/lib/stores/appstate.svelte.ts index 59af0dbf..2eee4d50 100644 --- a/browser/create-template/templates/sveltekit-site/src/lib/stores/appstate.svelte.ts +++ b/browser/create-template/templates/sveltekit-site/src/lib/stores/appstate.svelte.ts @@ -3,7 +3,3 @@ import { PUBLIC_WEBSITE_RESOURCE } from '$env/static/public'; export const appState = $state({ currentSubject: PUBLIC_WEBSITE_RESOURCE }); - -export const setCurrentSubject = (value: string) => { - appState.currentSubject = value; -}; diff --git a/browser/create-template/templates/sveltekit-site/src/routes/+layout.svelte b/browser/create-template/templates/sveltekit-site/src/routes/+layout.svelte index b145a47b..83f07f8c 100644 --- a/browser/create-template/templates/sveltekit-site/src/routes/+layout.svelte +++ b/browser/create-template/templates/sveltekit-site/src/routes/+layout.svelte @@ -6,6 +6,7 @@ import VStack from '$lib/components/Layout/VStack.svelte'; import { getStore } from '$lib/atomic/getStore'; + // We set the store on the AtomicStoreContext so it can be accessed by `getResource()`. const store = getStore(); createAtomicStoreContext(store); diff --git a/browser/lib/src/ontologies/core.ts b/browser/lib/src/ontologies/core.ts index ef494f24..899979f2 100644 --- a/browser/lib/src/ontologies/core.ts +++ b/browser/lib/src/ontologies/core.ts @@ -146,7 +146,7 @@ declare module '../index.js' { [core.properties.write]: string[]; [core.properties.publicKey]: string; [core.properties.instances]: string[]; - [core.properties.properties]: undefined; + [core.properties.properties]: string[]; [core.properties.classes]: string[]; [core.properties.isLocked]: boolean; [core.properties.localId]: string; diff --git a/browser/lib/src/ontologies/server.ts b/browser/lib/src/ontologies/server.ts index 7e9392cb..b282a1c9 100644 --- a/browser/lib/src/ontologies/server.ts +++ b/browser/lib/src/ontologies/server.ts @@ -17,34 +17,35 @@ export const server = { 'https://atomicdata.dev/ontology/server/class/endpoint-response', }, properties: { - drives: 'https://atomicdata.dev/properties/drives', - results: 'https://atomicdata.dev/properties/endpoint/results', - property: 'https://atomicdata.dev/properties/search/property', - redirectAgent: 'https://atomicdata.dev/properties/invite/redirectAgent', agent: 'https://atomicdata.dev/properties/invite/agent', - publicKey: 'https://atomicdata.dev/properties/invite/publicKey', - target: 'https://atomicdata.dev/properties/invite/target', - usagesLeft: 'https://atomicdata.dev/properties/invite/usagesLeft', - users: 'https://atomicdata.dev/properties/invite/users', - write: 'https://atomicdata.dev/properties/invite/write', - filename: 'https://atomicdata.dev/properties/filename', - filesize: 'https://atomicdata.dev/properties/filesize', - downloadUrl: 'https://atomicdata.dev/properties/downloadURL', - mimetype: 'https://atomicdata.dev/properties/mimetype', + altText: 'https://atomicdata.dev/ontology/server/property/alt-text', attachments: 'https://atomicdata.dev/properties/attachments', - createdBy: 'https://atomicdata.dev/properties/createdBy', checksum: 'https://atomicdata.dev/properties/checksum', - internalId: 'https://atomicdata.dev/properties/internalId', children: 'https://atomicdata.dev/properties/children', - parameters: 'https://atomicdata.dev/properties/endpoint/parameters', - destination: 'https://atomicdata.dev/properties/destination', - status: 'https://atomicdata.dev/ontology/server/property/status', - responseMessage: - 'https://atomicdata.dev/ontology/server/property/response-message', + createdBy: 'https://atomicdata.dev/properties/createdBy', defaultOntology: 'https://atomicdata.dev/ontology/server/property/default-ontology', - imageWidth: 'https://atomicdata.dev/properties/imageWidth', + destination: 'https://atomicdata.dev/properties/destination', + downloadUrl: 'https://atomicdata.dev/properties/downloadURL', + drives: 'https://atomicdata.dev/properties/drives', + filename: 'https://atomicdata.dev/properties/filename', + filesize: 'https://atomicdata.dev/properties/filesize', imageHeight: 'https://atomicdata.dev/properties/imageHeight', + imageWidth: 'https://atomicdata.dev/properties/imageWidth', + internalId: 'https://atomicdata.dev/properties/internalId', + mimetype: 'https://atomicdata.dev/properties/mimetype', + parameters: 'https://atomicdata.dev/properties/endpoint/parameters', + property: 'https://atomicdata.dev/properties/search/property', + publicKey: 'https://atomicdata.dev/properties/invite/publicKey', + redirectAgent: 'https://atomicdata.dev/properties/invite/redirectAgent', + responseMessage: + 'https://atomicdata.dev/ontology/server/property/response-message', + results: 'https://atomicdata.dev/properties/endpoint/results', + status: 'https://atomicdata.dev/ontology/server/property/status', + target: 'https://atomicdata.dev/properties/invite/target', + usagesLeft: 'https://atomicdata.dev/properties/invite/usagesLeft', + users: 'https://atomicdata.dev/properties/invite/users', + write: 'https://atomicdata.dev/properties/invite/write', }, __classDefs: { ['https://atomicdata.dev/classes/Error']: [ @@ -76,6 +77,7 @@ export const server = { 'https://atomicdata.dev/properties/internalId', 'https://atomicdata.dev/properties/imageWidth', 'https://atomicdata.dev/properties/imageHeight', + 'https://atomicdata.dev/ontology/server/property/alt-text', ], ['https://atomicdata.dev/classes/Invite']: [ 'https://atomicdata.dev/properties/invite/target', @@ -139,7 +141,8 @@ declare module '../index.js' { | typeof server.properties.mimetype | typeof server.properties.internalId | typeof server.properties.imageWidth - | typeof server.properties.imageHeight; + | typeof server.properties.imageHeight + | typeof server.properties.altText; }; [server.classes.invite]: { requires: BaseProps | typeof server.properties.target; @@ -159,60 +162,62 @@ declare module '../index.js' { } interface PropTypeMapping { + [server.properties.agent]: string; + [server.properties.altText]: string; + [server.properties.attachments]: string[]; + [server.properties.checksum]: string; + [server.properties.children]: string[]; + [server.properties.createdBy]: string; + [server.properties.defaultOntology]: string; + [server.properties.destination]: string; + [server.properties.downloadUrl]: string; [server.properties.drives]: string[]; - [server.properties.results]: string[]; + [server.properties.filename]: string; + [server.properties.filesize]: number; + [server.properties.imageHeight]: number; + [server.properties.imageWidth]: number; + [server.properties.internalId]: string; + [server.properties.mimetype]: string; + [server.properties.parameters]: string[]; [server.properties.property]: string; - [server.properties.redirectAgent]: string; - [server.properties.agent]: string; [server.properties.publicKey]: string; + [server.properties.redirectAgent]: string; + [server.properties.responseMessage]: string; + [server.properties.results]: string[]; + [server.properties.status]: number; [server.properties.target]: string; [server.properties.usagesLeft]: number; [server.properties.users]: string[]; [server.properties.write]: boolean; - [server.properties.filename]: string; - [server.properties.filesize]: number; - [server.properties.downloadUrl]: string; - [server.properties.mimetype]: string; - [server.properties.attachments]: string[]; - [server.properties.createdBy]: string; - [server.properties.checksum]: string; - [server.properties.internalId]: string; - [server.properties.children]: string[]; - [server.properties.parameters]: string[]; - [server.properties.destination]: string; - [server.properties.status]: number; - [server.properties.responseMessage]: string; - [server.properties.defaultOntology]: string; - [server.properties.imageWidth]: number; - [server.properties.imageHeight]: number; } interface PropSubjectToNameMapping { + [server.properties.agent]: 'agent'; + [server.properties.altText]: 'altText'; + [server.properties.attachments]: 'attachments'; + [server.properties.checksum]: 'checksum'; + [server.properties.children]: 'children'; + [server.properties.createdBy]: 'createdBy'; + [server.properties.defaultOntology]: 'defaultOntology'; + [server.properties.destination]: 'destination'; + [server.properties.downloadUrl]: 'downloadUrl'; [server.properties.drives]: 'drives'; - [server.properties.results]: 'results'; + [server.properties.filename]: 'filename'; + [server.properties.filesize]: 'filesize'; + [server.properties.imageHeight]: 'imageHeight'; + [server.properties.imageWidth]: 'imageWidth'; + [server.properties.internalId]: 'internalId'; + [server.properties.mimetype]: 'mimetype'; + [server.properties.parameters]: 'parameters'; [server.properties.property]: 'property'; - [server.properties.redirectAgent]: 'redirectAgent'; - [server.properties.agent]: 'agent'; [server.properties.publicKey]: 'publicKey'; + [server.properties.redirectAgent]: 'redirectAgent'; + [server.properties.responseMessage]: 'responseMessage'; + [server.properties.results]: 'results'; + [server.properties.status]: 'status'; [server.properties.target]: 'target'; [server.properties.usagesLeft]: 'usagesLeft'; [server.properties.users]: 'users'; [server.properties.write]: 'write'; - [server.properties.filename]: 'filename'; - [server.properties.filesize]: 'filesize'; - [server.properties.downloadUrl]: 'downloadUrl'; - [server.properties.mimetype]: 'mimetype'; - [server.properties.attachments]: 'attachments'; - [server.properties.createdBy]: 'createdBy'; - [server.properties.checksum]: 'checksum'; - [server.properties.internalId]: 'internalId'; - [server.properties.children]: 'children'; - [server.properties.parameters]: 'parameters'; - [server.properties.destination]: 'destination'; - [server.properties.status]: 'status'; - [server.properties.responseMessage]: 'responseMessage'; - [server.properties.defaultOntology]: 'defaultOntology'; - [server.properties.imageWidth]: 'imageWidth'; - [server.properties.imageHeight]: 'imageHeight'; } } diff --git a/browser/lib/src/ontology.ts b/browser/lib/src/ontology.ts index 416252b6..979a3949 100644 --- a/browser/lib/src/ontology.ts +++ b/browser/lib/src/ontology.ts @@ -52,7 +52,7 @@ export type InferTypeOfValueInTriple< : JSONValue, > = Returns; -type QuickAccesKnownPropType = { +type QuickAccessKnownPropType = { [Prop in keyof PropsOfClass as PropSubjectToNameMapping[Prop]]: InferTypeOfValueInTriple< Class, Prop @@ -60,9 +60,9 @@ type QuickAccesKnownPropType = { }; /** Type of the dynamically created resource.props field */ -export type QuickAccesPropType = +export type QuickAccessPropType = // eslint-disable-next-line @typescript-eslint/no-explicit-any - Class extends UnknownClass ? any : QuickAccesKnownPropType; + Class extends UnknownClass ? any : QuickAccessKnownPropType; export type OptionalClass = keyof Classes | UnknownClass; diff --git a/browser/lib/src/resource.ts b/browser/lib/src/resource.ts index 88cdd430..5b160295 100644 --- a/browser/lib/src/resource.ts +++ b/browser/lib/src/resource.ts @@ -16,9 +16,10 @@ import { server } from './ontologies/server.js'; import { getKnownClassDefBySubject, + getKnownNameBySubject, type InferTypeOfValueInTriple, type OptionalClass, - type QuickAccesPropType, + type QuickAccessPropType, } from './ontology.js'; import type { Store } from './store.js'; import { properties, instances, urls } from './urls.js'; @@ -100,7 +101,7 @@ export class Resource { return this._subject; } - /** A human readable title for the resource, returns first of eighter: name, shortname, filename or subject */ + /** A human readable title for the resource, returns first of either: name, shortname, filename or subject */ public get title(): string { return (this.get(core.properties.name) ?? this.get(core.properties.shortname) ?? @@ -112,7 +113,7 @@ export class Resource { * Dynamic prop accessor, only works for known properties registered via an ontology. * @example const description = resource.props.description */ - public get props(): QuickAccesPropType { + public get props(): QuickAccessPropType { const defaultProps = { parent: core.properties.parent, isA: core.properties.isA, @@ -128,10 +129,12 @@ export class Resource { .filter(def => def !== undefined); const getPropSubject = (name: string) => { + // Check if the property is a default property if (name in defaultProps) { return defaultProps[name]; } + // Check if the property is defined in any of the classes for (const def of defs) { const value = def[name]; @@ -139,9 +142,18 @@ export class Resource { return value; } } + + // Check if any of its propvals have the name + for (const key of this.propvals.keys()) { + const propName = getKnownNameBySubject(key); + + if (propName === name) { + return key; + } + } }; - return new Proxy({} as QuickAccesPropType, { + return new Proxy({} as QuickAccessPropType, { get(_target, propName) { const propSubject = getPropSubject(propName as string); diff --git a/browser/svelte/src/lib/stores/getResource.svelte.ts b/browser/svelte/src/lib/stores/getResource.svelte.ts index 99af8839..02010226 100644 --- a/browser/svelte/src/lib/stores/getResource.svelte.ts +++ b/browser/svelte/src/lib/stores/getResource.svelte.ts @@ -12,10 +12,10 @@ import { getStoreFromContext } from './store.js'; /** * Starts fetching a resource and adds it to the store. - * An empty resource will be returned immediately that updates when the resource is fetched. - * This way you can start rendering UI that + * Unless the resource was found in the cache, an empty resource will be returned immediately that updates when the resource is fetched. + * This way you can start rendering UI without having to wait for the resource to be fetched. * To check if the resource is ready, use `resource.loading`. - * Only works in components contexts. If you want to fetch a resource outside of a component, use `await store.getResource()`. + * Only works in component contexts. If you want to fetch a resource outside of a component, use `await store.getResource()`. * * You need to pass the subject as a function that returns a string to make it reactive. * @@ -76,6 +76,7 @@ export function getResource( }; }); + // Returning the resource directly would break the reactivity so we need to proxy it. return new Proxy(resource, { get(_, prop) { return resource[prop as keyof Resource]; diff --git a/docs/src/js-lib/resource.md b/docs/src/js-lib/resource.md index fb524cf1..0377b2a2 100644 --- a/docs/src/js-lib/resource.md +++ b/docs/src/js-lib/resource.md @@ -70,9 +70,9 @@ const category = resource.get( ## Writing Data -Writing data is done using the `.set` method (works on any resource) or by asigning to the props accessor (only works on annotated resources). +Writing data is done using the `.set` method (works on any resource) or by assigning to the props accessor (only works on annotated resources). -### Using .props: +### Using .props ```typescript import { type Article } from './ontologies/article'; @@ -89,7 +89,7 @@ await resource.save(); Setting values via `resource.props` does not validate the value against the properties datatype. Use the `resource.set()` method when you want to validate the value. -### Using .set(): +### Using .set() ```typescript import { core } from '@tomic/lib'; @@ -146,6 +146,7 @@ await resource.save(); > For example: `resource.props.likedBy.push('https://my-atomicserver.com/users/1')` will not work. **Parameters** + | Name | Type | Description | |----------|-----------|-----------------------------------------------------------------------------------------------------| | property | string | Subject of the property to push to | diff --git a/docs/src/js-lib/store.md b/docs/src/js-lib/store.md index f155d9cd..552a3cf9 100644 --- a/docs/src/js-lib/store.md +++ b/docs/src/js-lib/store.md @@ -15,7 +15,7 @@ It takes an object with the following options | Name | Type | Description | |-----------|---------------------|------------------------------------------------------------------------------------------------------------------------------------------| | serverUrl | string | URL of your atomic server | -| agent | [Agent](./agent.md) | **(optional)** The agent the store should use to fetch resources and to sign commits when editting resources, defaults to a public agent | +| agent | [Agent](./agent.md) | **(optional)** The agent the store should use to fetch resources and to sign commits when editing resources, defaults to a public agent | ```typescript const store = new Store({ diff --git a/docs/src/svelte.md b/docs/src/svelte.md index 50720e87..2bb2145e 100644 --- a/docs/src/svelte.md +++ b/docs/src/svelte.md @@ -4,10 +4,12 @@ An AtomicServer client for [Svelte](https://svelte.dev/). Makes fetching AtomicData easy. -Fetched resources are chached and reactive, they will update when the data changes, even when the resource was changed by someone else. +Fetched resources are cached and reactive, they will update when the data changes, even when the resource was changed by someone else. [See open source template: `atomic-sveltekit-demo` (outdated).](https://github.com/atomicdata-dev/atomic-sveltekit-demo) +> **Note:** As of version 0.41, @tomic/svelte requires Svelte 5 or later. Svelte 4 is not supported on versions above 0.40.0. + ## Quick Examples ### Getting a resource and displaying one of its properties @@ -31,7 +33,7 @@ Fetched resources are chached and reactive, they will update when the data chang import { type Core } from '@tomic/lib'; const resource = getResource(() => 'https://example.com/user1'); - +a @@ -88,7 +90,7 @@ You can now access this store from any sub component by using `getStoreFromConte const store = getStoreFromContext(); store.newResource({ isA: [dataBrowser.classes.Folder] - parant: 'some_other_subject', + parent: 'some_other_subject', }); ``` diff --git a/lib/defaults/ontologies.json b/lib/defaults/ontologies.json index 9d4be2c9..3ebab917 100644 --- a/lib/defaults/ontologies.json +++ b/lib/defaults/ontologies.json @@ -23,7 +23,10 @@ "https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/resourceArray", "https://atomicdata.dev/properties/description": "A list of properties", "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", - "https://atomicdata.dev/properties/shortname": "properties" + "https://atomicdata.dev/properties/shortname": "properties", + "https://atomicdata.dev/properties/isA": [ + "https://atomicdata.dev/classes/Property" + ] }, { "@id": "https://atomicdata.dev/properties/classes", @@ -41,7 +44,10 @@ "https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/resourceArray", "https://atomicdata.dev/properties/description": "A list of class instances", "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", - "https://atomicdata.dev/properties/shortname": "instances" + "https://atomicdata.dev/properties/shortname": "instances", + "https://atomicdata.dev/properties/isA": [ + "https://atomicdata.dev/classes/Property" + ] }, { "@id": "https://atomicdata.dev/ontology/server/property/default-ontology",