Skip to content

Commit d95df8a

Browse files
authored
Merge pull request #41 from SolidOS/discovery
Improvement to discovery
2 parents ec29db4 + 3f05728 commit d95df8a

5 files changed

Lines changed: 110 additions & 101 deletions

File tree

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"clean": "rm -rf lib",
99
"lint": "eslint ./src",
1010
"test": "jest",
11+
"test-debug": "node --inspect-brk ./node_modules/.bin/jest -i --env jest-environment-node-debug",
1112
"preversion": "npm test",
1213
"postversion": "git push --follow-tags",
1314
"prepublishOnly": "npm run build",
@@ -35,6 +36,7 @@
3536
"@typescript-eslint/parser": "^5.19.0",
3637
"eslint": "^8.13.0",
3738
"jest": "^27.5.1",
39+
"jest-environment-node-debug": "^2.0.0",
3840
"jest-fetch-mock": "^3.0.3",
3941
"typescript": "^4.6.3"
4042
},

src/discovery/discoveryLogic.ts

Lines changed: 65 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { NamedNode, LiveStore, sym, st } from 'rdflib'
12
import * as $rdf from 'rdflib'
2-
import { LiveStore, NamedNode, st, sym } from 'rdflib'
3+
import { solidLogicSingleton } from "../logic/solidLogicSingleton"
4+
import { newThing } from "../util/uri"
35
import solidNamespace from 'solid-namespace'
4-
import { solidLogicSingleton } from '../logic/solidLogicSingleton'
5-
import { newThing } from '../util/uri'
66

77
const { authn } = solidLogicSingleton
88
const { currentUser } = authn
@@ -18,30 +18,24 @@ type ScopedApp = { instance: NamedNode, scope: TypeIndexScope }
1818
* @param doc {NamedNode} - The resource
1919
*/
2020
export async function loadOrCreateIfNotExists (store: LiveStore, doc: NamedNode) {
21-
let response
22-
// console.log('@@ loadOrCreateIfNotExists doc ', doc)
23-
try {
24-
response = await store.fetcher.load(doc)
25-
} catch (err) {
26-
if (err.response.status === 404) {
27-
// console.log('createIfNotExists doc does NOT exist, will create: ' + doc)
28-
try {
29-
store.fetcher.webOperation('PUT', doc, {data: '', contentType: 'text/turtle'})
30-
} catch (err) {
31-
const msg = 'createIfNotExists: PUT FAILED: ' + doc + ': ' + err
32-
// console.log(msg)
33-
throw new Error(msg)
21+
let response
22+
try {
23+
response = await store.fetcher.load(doc)
24+
} catch (err) {
25+
if (err.response.status === 404) {
26+
try {
27+
store.fetcher.webOperation('PUT', doc, {data: '', contentType: 'text/turtle'})
28+
} catch (err) {
29+
const msg = 'createIfNotExists: PUT FAILED: ' + doc + ': ' + err
30+
throw new Error(msg)
31+
}
32+
delete store.fetcher.requested[doc.uri] // delete cached 404 error
33+
} else {
34+
const msg = 'createIfNotExists doc load error NOT 404: ' + doc + ': ' + err
35+
throw new Error(msg) // @@ add nested errors
3436
}
35-
delete store.fetcher.requested[doc.uri] // delete cached 404 error
36-
// console.log('createIfNotExists doc created ok ' + doc)
37-
} else {
38-
const msg = 'createIfNotExists doc load error NOT 404: ' + doc + ': ' + err
39-
// console.log(msg)
40-
throw new Error(msg) // @@ add nested errors
41-
}
42-
}
43-
// console.log('createIfNotExists doc exists, all good ' + doc)
44-
return response
37+
}
38+
return response
4539
}
4640

4741
export function suggestPreferencesFile (me:NamedNode) {
@@ -65,16 +59,13 @@ export function suggestPrivateTypeIndex (preferencesFile:NamedNode) {
6559
**
6660
** return: null no ld one and failed to make a new one
6761
*/
68-
export async function followOrCreateLink(store: LiveStore, subject: NamedNode, predicate: NamedNode,
69-
object: NamedNode, doc:NamedNode):Promise<NamedNode | null> {
62+
export async function followOrCreateLink (store: LiveStore, subject: NamedNode, predicate: NamedNode,
63+
object: NamedNode, doc:NamedNode):Promise<NamedNode | null> {
7064
await store.fetcher.load(doc)
7165
const result = store.any(subject, predicate, null, doc)
72-
// console.log('@@ followOrCreateLink result ', result)
7366

7467
if (result) return result as NamedNode
7568
if (!store.updater.editable(doc)) {
76-
// console.log(`followOrCreateLink: Can't modify ${doc} so can't make new link to ${object}.`)
77-
// console.log('followOrCreateLink @@ connectedStatements', store.connectedStatements(subject))
7869
return null
7970
}
8071
try {
@@ -84,67 +75,55 @@ export async function followOrCreateLink(store: LiveStore, subject: NamedNode, p
8475
return null
8576
}
8677

87-
// console.log(`Success making link in ${doc} to ${object}` )
88-
8978
try {
9079
await loadOrCreateIfNotExists(store, object)
9180
// store.fetcher.webOperation('PUT', object, { data: '', contentType: 'text/turtle'})
9281
} catch (err) {
9382
console.warn(`followOrCreateLink: Error loading or saving new linked document: ${object}: ${err}`)
9483
}
95-
// console.log(`followOrCreateLink: Success loading or saving new linked document: ${object}.`)
9684
return object
9785
}
9886

99-
export async function loadProfile(store: LiveStore, user: NamedNode) {
100-
// console.log(' @@ loadProfile: user', user)
87+
export async function loadProfile (store: LiveStore, user: NamedNode) {
10188
if (!user) {
10289
throw new Error(`loadProfile: no user given.`)
10390
}
104-
// try {
91+
try {
10592
await store.fetcher.load(user.doc())
106-
// } catch (err) {
107-
// throw new Error(`Unable to load profile of user ${user}: ${err}`)
108-
//}
93+
} catch (err) {
94+
throw new Error(`Unable to load profile of user ${user}: ${err}`)
95+
}
10996
return user.doc()
11097
}
11198

112-
export async function loadPreferences(store: LiveStore, user: NamedNode): Promise <NamedNode | undefined > {
113-
// console.log('loadPreferences @@ user', user)
99+
export async function loadPreferences (store: LiveStore, user: NamedNode): Promise <NamedNode | undefined > {
114100
await loadProfile(store as LiveStore, user)
115101

116102
const possiblePreferencesFile = suggestPreferencesFile(user)
117103

118104
const preferencesFile = await followOrCreateLink(store, user, ns.space('preferencesFile') as NamedNode, possiblePreferencesFile, user.doc())
119105

120-
// console.log('loadPreferences @@ pref file', preferencesFile)
121106
if (!preferencesFile) {
122107
const message = `User ${user} has no pointer in profile to preferences file.`
123108
console.warn(message)
124-
// throw new Error()
125109
return undefined
126110
}
127111
try {
128112
await store.fetcher.load(preferencesFile as NamedNode)
129113
} catch (err) { // Maybe a permission propblem or origin problem
130114
return undefined
131-
// throw new Error(`Unable to load preferences file ${preferencesFile} of user <${user}>: ${err}`)
132115
}
133116
return preferencesFile as NamedNode
134117
}
135118

136-
export async function loadTypeIndexesFor(store: LiveStore, user:NamedNode): Promise<Array<TypeIndexScope>> {
137-
// console.log('@@ loadTypeIndexesFor user', user)
119+
export async function loadTypeIndexesFor (store: LiveStore, user:NamedNode): Promise<Array<TypeIndexScope>> {
138120
if (!user) throw new Error(`loadTypeIndexesFor: No user given`)
139121
const profile = await loadProfile(store, user)
140122

141123
const suggestion = suggestPublicTypeIndex(user)
142124

143125
const publicTypeIndex = await followOrCreateLink(store, user, ns.solid('publicTypeIndex') as NamedNode, suggestion, profile)
144126

145-
// const publicTypeIndex = store.any(user, ns.solid('publicTypeIndex'), undefined, profile)
146-
// console.log('@@ loadTypeIndexesFor publicTypeIndex', publicTypeIndex)
147-
148127
const publicScopes = publicTypeIndex ? [ { label: 'public', index: publicTypeIndex as NamedNode, agent: user } ] : []
149128

150129
let preferencesFile
@@ -171,7 +150,6 @@ export async function loadTypeIndexesFor(store: LiveStore, user:NamedNode): Prom
171150
const scopes = publicScopes.concat(privateScopes)
172151
if (scopes.length === 0) return scopes
173152
const files = scopes.map(scope => scope.index)
174-
// console.log('@@ loadTypeIndexesFor files ', files)
175153
try {
176154
await store.fetcher.load(files)
177155
} catch (err) {
@@ -183,14 +161,13 @@ export async function loadTypeIndexesFor(store: LiveStore, user:NamedNode): Prom
183161
export async function loadCommunityTypeIndexes (store:LiveStore, user:NamedNode): Promise<TypeIndexScope[][]> {
184162
const preferencesFile = await loadPreferences(store, user)
185163
if (preferencesFile) { // For now, pick up communities as simple links from the preferences file.
186-
const communities = store.each(user, ns.solid('community'), undefined, preferencesFile as NamedNode)
187-
// console.log('loadCommunityTypeIndexes communities: ',communities)
164+
const communities = store.each(user, ns.solid('community'), undefined, preferencesFile as NamedNode).concat(
165+
store.each(user, ns.solid('community'), undefined, user.doc() as NamedNode)
166+
)
188167
let result = []
189168
for (const org of communities) {
190169
result = result.concat(await loadTypeIndexesFor(store, org as NamedNode) as any)
191170
}
192-
// const communityTypeIndexesPromises = communities.map(async community => await loadTypeIndexesFor(store, community as NamedNode))
193-
// const result1 = Promise.all(communityTypeIndexesPromises)
194171
return result
195172
}
196173
return [] // No communities
@@ -209,38 +186,36 @@ export function uniqueNodes (arr: NamedNode[]): NamedNode[] {
209186
return arr2 // Array.from(new Set(arr.map(x => x.uri))).map(u => sym(u))
210187
}
211188

212-
export async function getScopedAppsfromIndex (store, scope, theClass: NamedNode) {
213-
// console.log(`getScopedAppsfromIndex agent ${scope.agent} index: ${scope.index}` )
189+
export async function getScopedAppsFromIndex (store, scope, theClass: NamedNode | null) {
214190
const index = scope.index
215-
const registrations = store.each(undefined, ns.solid('forClass'), theClass, index)
216-
// console.log(' registrations', registrations )
217-
218-
const directInstances = registrations.map(reg => store.each(reg as NamedNode, ns.solid('instance'), null, index)).flat()
219-
// console.log(' directInstances', directInstances )
191+
const registrations = store.statementsMatching(null, ns.solid('instance'), null, index)
192+
.concat(store.statementsMatching(null, ns.solid('instanceContainer'), null, index))
193+
.map(st => st.subject)
194+
const relevant = theClass ? registrations.filter(reg => store.any(reg, ns.solid('forClass'), null, index).sameTerm(theClass))
195+
: registrations
196+
const directInstances = relevant.map(reg => store.each(reg as NamedNode, ns.solid('instance'), null, index)).flat()
220197
let instances = uniqueNodes(directInstances)
221198

222-
const instanceContainers = registrations.map(
223-
reg => store.each(reg as NamedNode, ns.solid('instanceContainer'), null, index)
224-
).flat()
199+
const instanceContainers = relevant.map(
200+
reg => store.each(reg as NamedNode, ns.solid('instanceContainer'), null, index)).flat()
225201

226202
// instanceContainers may be deprocatable if no one has used them
227203
const containers = uniqueNodes(instanceContainers)
204+
if (containers.length > 0) { console.log('@@ getScopedAppsFromIndex containers ', containers)}
228205
for (let i = 0; i < containers.length; i++) {
229206
const cont = containers[i]
230207
await store.fetcher.load(cont)
231208
const contents = store.each(cont, ns.ldp('contains'), null, cont)
232-
// if (contents.length) console.log('getScopedAppsfromIndex @@ instanceContainer contents:', contents)
233209
instances = instances.concat(contents)
234210
}
235211
return instances.map(instance => { return {instance, scope}})
236212
}
237213

238214
export async function getScopedAppInstances (store:LiveStore, klass: NamedNode, user: NamedNode):Promise<ScopedApp[]> {
239-
// console.log('getScopedAppInstances @@ ' + user)
240215
const scopes = await loadAllTypeIndexes(store, user)
241216
let scopedApps = []
242217
for (const scope of scopes) {
243-
const scopedApps0 = await getScopedAppsfromIndex(store, scope, klass) as any
218+
const scopedApps0 = await getScopedAppsFromIndex(store, scope, klass) as any
244219
scopedApps = scopedApps.concat(scopedApps0)
245220
}
246221
return scopedApps
@@ -268,21 +243,27 @@ export async function registerInstanceInTypeIndex (
268243
theClass: NamedNode,
269244
// agent: NamedNode
270245
): Promise<NamedNode | null> {
271-
const registration = newThing(index)
272-
const ins = [
273-
// See https://github.com/solid/solid/blob/main/proposals/data-discovery.md
274-
st(registration, ns.rdf('type'), ns.solid('TypeRegistration'), index),
275-
st(registration, ns.solid('forClass'), theClass, index),
276-
st(registration, ns.solid('instance'), instance, index)
277-
]
278-
try {
279-
console.log('patching index', ins)
280-
await store.updater.update([], ins)
281-
} catch (err) {
282-
const msg = `Unable to register ${instance} in index ${index}: ${err}`
283-
console.warn(msg)
284-
return null
285-
}
286-
return registration
246+
const registration = newThing(index)
247+
const ins = [
248+
// See https://github.com/solid/solid/blob/main/proposals/data-discovery.md
249+
st(registration, ns.rdf('type'), ns.solid('TypeRegistration'), index),
250+
st(registration, ns.solid('forClass'), theClass, index),
251+
st(registration, ns.solid('instance'), instance, index)
252+
]
253+
try {
254+
await store.updater.update([], ins)
255+
} catch (err) {
256+
const msg = `Unable to register ${instance} in index ${index}: ${err}`
257+
console.warn(msg)
258+
return null
259+
}
260+
return registration
261+
}
262+
263+
export async function deleteTypeIndexRegistration (store: LiveStore, item) {
264+
const reg = store.the(null, ns.solid('instance'), item.instance, item.scope.index) as NamedNode
265+
if (!reg) throw new Error(`deleteTypeIndexRegistration: No registration found for ${item.instance}`)
266+
const statements = store.statementsMatching(reg, null, null, item.scope.index)
267+
await store.updater.update(statements, [])
287268
}
288269
// ENDS

src/index.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,25 @@ export {
2020
loadIndex
2121
} from './typeIndex/typeIndexLogic'
2222

23+
// Generate by
24+
// grep export src/discovery/discoveryLogic.ts | sed -e 's/export //g' | sed -e 's/async //g'| sed -e 's/function //g' | sed -e 's/ .*/,/g' | sort
2325
export {
24-
loadProfile,
26+
deleteTypeIndexRegistration,
27+
followOrCreateLink,
28+
getAppInstances,
29+
getScopedAppInstances,
30+
getScopedAppsFromIndex,
31+
loadAllTypeIndexes,
32+
loadCommunityTypeIndexes,
33+
loadOrCreateIfNotExists,
2534
loadPreferences,
35+
loadProfile,
2636
loadTypeIndexesFor,
27-
loadCommunityTypeIndexes,
28-
loadAllTypeIndexes
37+
registerInstanceInTypeIndex,
38+
suggestPreferencesFile,
39+
suggestPrivateTypeIndex,
40+
suggestPublicTypeIndex,
41+
uniqueNodes
2942
} from './discovery/discoveryLogic'
3043

3144
export { SolidLogic } from './logic/SolidLogic'

test/discoveryLogic.test.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -363,29 +363,29 @@ const ClubScopes =
363363
},
364364
"label": "public",
365365
},
366-
{
367-
"agent": {
366+
{
367+
"agent": {
368368
"classOrder": 5,
369369
"termType": "NamedNode",
370370
"value": "https://club.example.com/profile/card.ttl#it",
371371
},
372-
"index": {
372+
"index": {
373373
"classOrder": 5,
374374
"termType": "NamedNode",
375375
"value": "https://club.example.com/settings/private-type-index.ttl",
376376
},
377377
"label": "private",
378-
},
379-
];
380-
describe('loadCommunityTypeIndexes', () => {
381-
it('exists', () => {
382-
expect(loadCommunityTypeIndexes).toBeInstanceOf(Function)
383-
})
384-
it('loads data', async () => {
385-
const result = await loadCommunityTypeIndexes(store, user) // @@ tbd
386-
expect(result).toEqual(ClubScopes)
387-
})
388-
})
378+
}
379+
]
380+
describe('loadCommunityTypeIndexes', () => {
381+
it('exists', () => {
382+
expect(loadCommunityTypeIndexes).toBeInstanceOf(Function)
383+
})
384+
it('loads data', async () => {
385+
const result = await loadCommunityTypeIndexes(store, user)
386+
expect(result).toEqual(ClubScopes)
387+
})
388+
})
389389

390390
const AliceAndClubScopes =
391391
[

0 commit comments

Comments
 (0)