From 218574879d6cb8211f56d9da2f25c0406668c34c Mon Sep 17 00:00:00 2001 From: Huseyin Kir <73784267+hkir-dev@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:44:52 +0000 Subject: [PATCH 1/5] email and telephone objects added --- package.json | 2 +- src/solid/Email.ts | 20 ++++++++++++++++++++ src/solid/EmailDataset.ts | 9 +++++++++ src/solid/Telephone.ts | 20 ++++++++++++++++++++ src/solid/TelephoneDataset.ts | 9 +++++++++ src/solid/mod.ts | 4 ++++ 6 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/solid/Email.ts create mode 100644 src/solid/EmailDataset.ts create mode 100644 src/solid/Telephone.ts create mode 100644 src/solid/TelephoneDataset.ts diff --git a/package.json b/package.json index d24e608..7e07862 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ ], "license": "MIT", "dependencies": { - "rdfjs-wrapper": "^0.15.0" + "rdfjs-wrapper": "^0.28.0" }, "devDependencies": { "@rdfjs/types": "^2", diff --git a/src/solid/Email.ts b/src/solid/Email.ts new file mode 100644 index 0000000..18e6715 --- /dev/null +++ b/src/solid/Email.ts @@ -0,0 +1,20 @@ +import { TermWrapper, ValueMapping, TermMapping } from 'rdfjs-wrapper'; +import { VCARD, RDF } from '../vocabulary/mod.js'; + +export class Email extends TermWrapper { + get emailAddress(): string { + return this.singular(VCARD.value, ValueMapping.literalToString); + } + + set emailAddress(value: string) { + this.overwrite(VCARD.value, value, TermMapping.stringToLiteral); + } + + get emailType(): string | undefined { + return this.singularNullable(RDF.type, ValueMapping.iriToString); + } + + set emailType(value: string | undefined) { + this.overwriteNullable(RDF.type, value, TermMapping.stringToIri); + } +} diff --git a/src/solid/EmailDataset.ts b/src/solid/EmailDataset.ts new file mode 100644 index 0000000..ca53e69 --- /dev/null +++ b/src/solid/EmailDataset.ts @@ -0,0 +1,9 @@ +import { DatasetWrapper } from 'rdfjs-wrapper'; +import { VCARD } from '../vocabulary/mod.js'; +import { Email } from './Email.js'; + +export class EmailDataset extends DatasetWrapper { + get email(): Iterable { + return this.objectsOf(VCARD.hasEmail, Email); + } +} diff --git a/src/solid/Telephone.ts b/src/solid/Telephone.ts new file mode 100644 index 0000000..088b55b --- /dev/null +++ b/src/solid/Telephone.ts @@ -0,0 +1,20 @@ +import { TermWrapper, ValueMapping, TermMapping } from 'rdfjs-wrapper'; +import { VCARD } from '../vocabulary/mod.js'; + +export class Telephone extends TermWrapper { + get phoneNumber(): string { + return this.singular(VCARD.hasValue, ValueMapping.literalToString) || ''; + } + + set phoneNumber(value: string) { + this.overwrite(VCARD.hasValue, value, TermMapping.stringToLiteral); + } + + get phoneType(): string | undefined { + return this.singularNullable(VCARD.telephoneType, ValueMapping.iriToString); + } + + set phoneType(value: string | undefined) { + this.overwriteNullable(VCARD.telephoneType, value, TermMapping.stringToIri); + } +} diff --git a/src/solid/TelephoneDataset.ts b/src/solid/TelephoneDataset.ts new file mode 100644 index 0000000..889e0a7 --- /dev/null +++ b/src/solid/TelephoneDataset.ts @@ -0,0 +1,9 @@ +import { DatasetWrapper } from 'rdfjs-wrapper'; +import { VCARD } from '../vocabulary/vcard.js'; +import { Telephone } from './Telephone'; + +export class TelephoneDataset extends DatasetWrapper { + get telephone(): Iterable { + return this.objectsOf(VCARD.hasTelephone, Telephone); + } +} diff --git a/src/solid/mod.ts b/src/solid/mod.ts index fb84c5f..16c242a 100644 --- a/src/solid/mod.ts +++ b/src/solid/mod.ts @@ -1,3 +1,7 @@ export * from "./Container.js" export * from "./ContainerDataset.js" export * from "./Resource.js" +export * from "./Email.js"; +export * from "./EmailDataset.js"; +export * from "./Telephone.js"; +export * from "./TelephoneDataset.js"; From 91540be90dde358fb1c58f5712b93e279c215fb1 Mon Sep 17 00:00:00 2001 From: Huseyin Kir <73784267+hkir-dev@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:23:38 +0000 Subject: [PATCH 2/5] rollback rdfjs-wrapper version upgrade --- package.json | 2 +- src/solid/Email.ts | 10 +++++----- src/solid/Telephone.ts | 10 +++++----- src/vocabulary/vcard.ts | 23 +++++++++++++---------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 7e07862..d24e608 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ ], "license": "MIT", "dependencies": { - "rdfjs-wrapper": "^0.28.0" + "rdfjs-wrapper": "^0.15.0" }, "devDependencies": { "@rdfjs/types": "^2", diff --git a/src/solid/Email.ts b/src/solid/Email.ts index 18e6715..505e4e0 100644 --- a/src/solid/Email.ts +++ b/src/solid/Email.ts @@ -1,20 +1,20 @@ -import { TermWrapper, ValueMapping, TermMapping } from 'rdfjs-wrapper'; +import { TermWrapper, ValueMappings, TermMappings } from 'rdfjs-wrapper'; import { VCARD, RDF } from '../vocabulary/mod.js'; export class Email extends TermWrapper { get emailAddress(): string { - return this.singular(VCARD.value, ValueMapping.literalToString); + return this.singular(VCARD.value, ValueMappings.literalToString); } set emailAddress(value: string) { - this.overwrite(VCARD.value, value, TermMapping.stringToLiteral); + this.overwrite(VCARD.value, value, TermMappings.stringToLiteral); } get emailType(): string | undefined { - return this.singularNullable(RDF.type, ValueMapping.iriToString); + return this.singularNullable(RDF.type, ValueMappings.iriToString); } set emailType(value: string | undefined) { - this.overwriteNullable(RDF.type, value, TermMapping.stringToIri); + this.overwriteNullable(RDF.type, value, TermMappings.stringToIri); } } diff --git a/src/solid/Telephone.ts b/src/solid/Telephone.ts index 088b55b..5e96ea3 100644 --- a/src/solid/Telephone.ts +++ b/src/solid/Telephone.ts @@ -1,20 +1,20 @@ -import { TermWrapper, ValueMapping, TermMapping } from 'rdfjs-wrapper'; +import { TermWrapper, ValueMappings, TermMappings } from 'rdfjs-wrapper'; import { VCARD } from '../vocabulary/mod.js'; export class Telephone extends TermWrapper { get phoneNumber(): string { - return this.singular(VCARD.hasValue, ValueMapping.literalToString) || ''; + return this.singular(VCARD.hasValue, ValueMappings.literalToString) || ''; } set phoneNumber(value: string) { - this.overwrite(VCARD.hasValue, value, TermMapping.stringToLiteral); + this.overwrite(VCARD.hasValue, value, TermMappings.stringToLiteral); } get phoneType(): string | undefined { - return this.singularNullable(VCARD.telephoneType, ValueMapping.iriToString); + return this.singularNullable(VCARD.telephoneType, ValueMappings.iriToString); } set phoneType(value: string | undefined) { - this.overwriteNullable(VCARD.telephoneType, value, TermMapping.stringToIri); + this.overwriteNullable(VCARD.telephoneType, value, TermMappings.stringToIri); } } diff --git a/src/vocabulary/vcard.ts b/src/vocabulary/vcard.ts index 99ae2d3..f5f640d 100644 --- a/src/vocabulary/vcard.ts +++ b/src/vocabulary/vcard.ts @@ -1,12 +1,15 @@ - export const VCARD = { - fn: "http://www.w3.org/2006/vcard/ns#fn", - hasEmail: "http://www.w3.org/2006/vcard/ns#hasEmail", - hasValue: "http://www.w3.org/2006/vcard/ns#hasValue", - hasPhoto: "http://www.w3.org/2006/vcard/ns#hasPhoto", - hasTelephone: "http://www.w3.org/2006/vcard/ns#hasTelephone", - title: "http://www.w3.org/2006/vcard/ns#title", - hasUrl: "http://www.w3.org/2006/vcard/ns#hasUrl", - organizationName: "http://www.w3.org/2006/vcard/ns#organization-name", - role: "http://www.w3.org/2006/vcard/ns#organization-name", + fn: "http://www.w3.org/2006/vcard/ns#fn", + email: "http://www.w3.org/2006/vcard/ns#email", + hasEmail: "http://www.w3.org/2006/vcard/ns#hasEmail", + hasValue: "http://www.w3.org/2006/vcard/ns#hasValue", + hasPhoto: "http://www.w3.org/2006/vcard/ns#hasPhoto", + hasTelephone: "http://www.w3.org/2006/vcard/ns#hasTelephone", + title: "http://www.w3.org/2006/vcard/ns#title", + hasUrl: "http://www.w3.org/2006/vcard/ns#hasUrl", + organizationName: "http://www.w3.org/2006/vcard/ns#organization-name", + phone: "http://www.w3.org/2006/vcard/ns#phone", + role: "http://www.w3.org/2006/vcard/ns#role", + value: "http://www.w3.org/2006/vcard/ns#value", + telephoneType: "http://www.w3.org/2006/vcard/ns#TelephoneType", } as const; From 91e63a45c5bebbcde71f96b6b23891639f4f6c04 Mon Sep 17 00:00:00 2001 From: Huseyin Kir <73784267+hkir-dev@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:08:18 +0000 Subject: [PATCH 3/5] typo fix --- src/solid/TelephoneDataset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solid/TelephoneDataset.ts b/src/solid/TelephoneDataset.ts index 889e0a7..b67b269 100644 --- a/src/solid/TelephoneDataset.ts +++ b/src/solid/TelephoneDataset.ts @@ -1,6 +1,6 @@ import { DatasetWrapper } from 'rdfjs-wrapper'; import { VCARD } from '../vocabulary/vcard.js'; -import { Telephone } from './Telephone'; +import { Telephone } from './Telephone.js'; export class TelephoneDataset extends DatasetWrapper { get telephone(): Iterable { From e9f4473e8110381edcfec829245ae369f6f0387f Mon Sep 17 00:00:00 2001 From: Huseyin Kir <73784267+hkir-dev@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:37:05 +0000 Subject: [PATCH 4/5] unit tests added --- test/unit/email.test.ts | 85 +++++++++++++++++++++++++++++ test/unit/telephone.test.ts | 105 ++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 test/unit/email.test.ts create mode 100644 test/unit/telephone.test.ts diff --git a/test/unit/email.test.ts b/test/unit/email.test.ts new file mode 100644 index 0000000..04d286a --- /dev/null +++ b/test/unit/email.test.ts @@ -0,0 +1,85 @@ +import { DataFactory, Parser, Store } from "n3" +import assert from "node:assert" +import { describe, it } from "node:test" + +import { Email } from "@solid/object"; + +describe("Email tests", () => { + + const sampleRDF = ` +@prefix vcard: . +@prefix rdf: . + + + a vcard:Individual ; + vcard:fn "Alice" ; + vcard:hasEmail . + + + a vcard:Email ; + vcard:value "alice@example.org" ; + rdf:type vcard:Work . +`; + + it("should parse and retrieve email address", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const email = new Email( + DataFactory.namedNode("https://example.org/email/1"), + store, + DataFactory + ) + + assert.equal(email.emailAddress, "alice@example.org") + assert.equal(typeof email.emailAddress, "string") + }) + + it("should allow setting email address", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const email = new Email( + DataFactory.namedNode("https://example.org/email/1"), + store, + DataFactory + ) + + email.emailAddress = "bob@example.org" + + assert.equal(email.emailAddress, "bob@example.org") + }) + + it("should parse and retrieve email type", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const email = new Email( + DataFactory.namedNode("https://example.org/email/1"), + store, + DataFactory + ) + + const emailType = DataFactory.namedNode("http://www.w3.org/2006/vcard/ns#Home") + + assert.ok(emailType !== undefined) + assert.equal(typeof emailType, "object") + assert.equal(emailType.value, "http://www.w3.org/2006/vcard/ns#Home") + }) + + it("should allow setting email type", () => { + const store = new Store() + + const email = new Email( + DataFactory.namedNode("https://example.org/email/2"), + store, + DataFactory + ) + + email.emailAddress = "test@example.org" + email.emailType = "http://www.w3.org/2006/vcard/ns#Home" + + assert.equal(email.emailType, "http://www.w3.org/2006/vcard/ns#Home") + }) + +}) diff --git a/test/unit/telephone.test.ts b/test/unit/telephone.test.ts new file mode 100644 index 0000000..29b8ebd --- /dev/null +++ b/test/unit/telephone.test.ts @@ -0,0 +1,105 @@ +import { DataFactory, Parser, Store } from "n3" +import assert from "node:assert" +import { describe, it } from "node:test" + +import { Telephone } from "@solid/object"; + +describe("Telephone tests", () => { + + const sampleRDF = ` +@prefix vcard: . + + + a vcard:Individual ; + vcard:fn "Alice" ; + vcard:hasTelephone . + + + a vcard:Telephone ; + vcard:hasValue "+1234567890" ; + vcard:TelephoneType vcard:Cell . +`; + + it("should parse and retrieve phone number", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/1"), + store, + DataFactory + ) + + assert.equal(telephone.phoneNumber, "+1234567890") + assert.equal(typeof telephone.phoneNumber, "string") + }) + + it("should allow setting phone number", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/1"), + store, + DataFactory + ) + + telephone.phoneNumber = "+0987654321" + + assert.equal(telephone.phoneNumber, "+0987654321") + }) + + it("should parse and retrieve phone type", () => { + const store = new Store() + store.addQuads(new Parser().parse(sampleRDF)) + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/1"), + store, + DataFactory + ) + + const phoneType = telephone.phoneType + + assert.ok(phoneType !== undefined) + assert.equal(typeof phoneType, "string") + assert.equal(phoneType, "http://www.w3.org/2006/vcard/ns#Cell") + }) + + it("should allow setting phone type", () => { + const store = new Store() + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/2"), + store, + DataFactory + ) + + telephone.phoneNumber = "+1112223333" + telephone.phoneType = "http://www.w3.org/2006/vcard/ns#Car" + + assert.equal(telephone.phoneType, "http://www.w3.org/2006/vcard/ns#Car") + }) + + it("should throw when phone number is missing", () => { + const noPhoneRDF = ` +@prefix vcard: . + + + a vcard:Telephone . +` + const store = new Store() + store.addQuads(new Parser().parse(noPhoneRDF)) + + const telephone = new Telephone( + DataFactory.namedNode("https://example.org/phone/empty"), + store, + DataFactory + ) + + assert.throws(() => { + telephone.phoneNumber + }) + }) + +}) From 9cae83d3bb86c891fa14466a7e9e1ca59db474fb Mon Sep 17 00:00:00 2001 From: Huseyin Kir <73784267+hkir-dev@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:44:12 +0000 Subject: [PATCH 5/5] equivalent properties handled --- src/solid/EmailDataset.ts | 10 ++++++++-- src/solid/TelephoneDataset.ts | 9 +++++++-- src/vocabulary/vcard.ts | 2 ++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/solid/EmailDataset.ts b/src/solid/EmailDataset.ts index ca53e69..a45557a 100644 --- a/src/solid/EmailDataset.ts +++ b/src/solid/EmailDataset.ts @@ -3,7 +3,13 @@ import { VCARD } from '../vocabulary/mod.js'; import { Email } from './Email.js'; export class EmailDataset extends DatasetWrapper { - get email(): Iterable { - return this.objectsOf(VCARD.hasEmail, Email); + get emails(): Iterable { + const objects = new Set([ + ...this.instancesOf(VCARD.Email, Email), + ...this.objectsOf(VCARD.hasEmail, Email), + ...this.objectsOf(VCARD.email, Email), + ]) + + return objects } } diff --git a/src/solid/TelephoneDataset.ts b/src/solid/TelephoneDataset.ts index b67b269..b3f052c 100644 --- a/src/solid/TelephoneDataset.ts +++ b/src/solid/TelephoneDataset.ts @@ -3,7 +3,12 @@ import { VCARD } from '../vocabulary/vcard.js'; import { Telephone } from './Telephone.js'; export class TelephoneDataset extends DatasetWrapper { - get telephone(): Iterable { - return this.objectsOf(VCARD.hasTelephone, Telephone); + get telephones(): Iterable { + const objects = new Set([ + ...this.objectsOf(VCARD.hasTelephone, Telephone), + ...this.objectsOf(VCARD.tel, Telephone), + ]) + + return objects } } diff --git a/src/vocabulary/vcard.ts b/src/vocabulary/vcard.ts index f5f640d..19a3b39 100644 --- a/src/vocabulary/vcard.ts +++ b/src/vocabulary/vcard.ts @@ -1,9 +1,11 @@ export const VCARD = { fn: "http://www.w3.org/2006/vcard/ns#fn", + Email: "http://www.w3.org/2006/vcard/ns#Email", email: "http://www.w3.org/2006/vcard/ns#email", hasEmail: "http://www.w3.org/2006/vcard/ns#hasEmail", hasValue: "http://www.w3.org/2006/vcard/ns#hasValue", hasPhoto: "http://www.w3.org/2006/vcard/ns#hasPhoto", + tel: "http://www.w3.org/2006/vcard/ns#tel", hasTelephone: "http://www.w3.org/2006/vcard/ns#hasTelephone", title: "http://www.w3.org/2006/vcard/ns#title", hasUrl: "http://www.w3.org/2006/vcard/ns#hasUrl",