Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vector embeddings in prolog #537

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 112 additions & 82 deletions core/src/subject/SubjectEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export type QueryPartialEntity<T> = {
[P in keyof T]?: T[P] | (() => string);
};


/**
* Class representing a subject entity.
* Can extend this class to create a new subject entity to add methods interact with SDNA and much better experience then using the bare bone methods.
Expand All @@ -17,17 +16,16 @@ export class SubjectEntity {
#baseExpression: string;
#subjectClass: string;
#source: string;
#perspective: PerspectiveProxy
#perspective: PerspectiveProxy;
author: string;
timestamp: string;


/**
* Constructs a new subject.
* @param perspective - The perspective that the subject belongs to.
* @param baseExpression - The base expression of the subject.
* @param soruce - The source of the subject, the expression this instance is linked too.
*/
/**
* Constructs a new subject.
* @param perspective - The perspective that the subject belongs to.
* @param baseExpression - The base expression of the subject.
* @param soruce - The source of the subject, the expression this instance is linked too.
*/
constructor(perspective: PerspectiveProxy, baseExpression?: string, source?: string) {
this.#baseExpression = baseExpression ? baseExpression : Literal.from(makeRandomPrologAtom(24)).toUrl();
this.#perspective = perspective;
Expand All @@ -38,78 +36,105 @@ export class SubjectEntity {
* Gets the base expression of the subject.
*/
get baseExpression() {
return this.#baseExpression
return this.#baseExpression;
}

async ensureSubject() {
if (typeof this.#subjectClass === "string") return;
await this.#perspective.ensureSDNASubjectClass(this.#subjectClass);
}

private async getData(id?: string) {
const tempId = id ?? this.#baseExpression;
let data = await this.#perspective.getSubjectData(this.#subjectClass, tempId)
let data = await this.#perspective.getSubjectData(this.#subjectClass, tempId);
Object.assign(this, data);
this.#baseExpression = tempId;
return this
return this;
}

private async setProperty(key: string, value: any) {
const setters = await this.#perspective.infer(`subject_class("${this.#subjectClass}", C), property_setter(C, "${key}", Setter)`)
const setters = await this.#perspective.infer(
`subject_class("${this.#subjectClass}", C), property_setter(C, "${key}", Setter)`
);
if (setters && setters.length > 0) {
const actions = eval(setters[0].Setter)
const resolveLanguageResults = await this.#perspective.infer(`subject_class("${this.#subjectClass}", C), property_resolve_language(C, "${key}", Language)`)
let resolveLanguage
const actions = eval(setters[0].Setter);
const resolveLanguageResults = await this.#perspective.infer(
`subject_class("${this.#subjectClass}", C), property_resolve_language(C, "${key}", Language)`
);
let resolveLanguage;
if (resolveLanguageResults && resolveLanguageResults.length > 0) {
resolveLanguage = resolveLanguageResults[0].Language
resolveLanguage = resolveLanguageResults[0].Language;
}

if (resolveLanguage) {
value = await this.#perspective.createExpression(value, resolveLanguage)
value = await this.#perspective.createExpression(value, resolveLanguage);
}
await this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value }])
await this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value }]);
}
}

private async setCollectionSetter(key: string, value: any) {
let collectionSetters = await this.#perspective.infer(`subject_class("${this.#subjectClass}", C), collection_setter(C, "${singularToPlural(key)}", Setter)`)
if (!collectionSetters) collectionSetters = []
let collectionSetters = await this.#perspective.infer(
`subject_class("${this.#subjectClass}", C), collection_setter(C, "${singularToPlural(key)}", Setter)`
);
if (!collectionSetters) collectionSetters = [];

if (collectionSetters.length > 0) {
const actions = eval(collectionSetters[0].Setter)
const actions = eval(collectionSetters[0].Setter);

if (value) {
if (Array.isArray(value)) {
await this.#perspective.executeAction(actions, this.#baseExpression, value.map(v => ({ name: "value", value: v })))
await this.#perspective.executeAction(
actions,
this.#baseExpression,
value.map((v) => ({ name: "value", value: v }))
);
} else {
await this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value }])
await this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value }]);
}
}
}
}

private async setCollectionAdder(key: string, value: any) {
let adders = await this.#perspective.infer(`subject_class("${this.#subjectClass}", C), collection_adder(C, "${singularToPlural(key)}", Adder)`)
if (!adders) adders = []
let adders = await this.#perspective.infer(
`subject_class("${this.#subjectClass}", C), collection_adder(C, "${singularToPlural(key)}", Adder)`
);
if (!adders) adders = [];

if (adders.length > 0) {
const actions = eval(adders[0].Adder)
const actions = eval(adders[0].Adder);
if (value) {
if (Array.isArray(value)) {
await Promise.all(value.map(v => this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value: v }])))
await Promise.all(
value.map((v) =>
this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value: v }])
)
);
} else {
await this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value }])
await this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value }]);
}
}
}
}

private async setCollectionRemover(key: string, value: any) {
let removers = await this.#perspective.infer(`subject_class("${this.#subjectClass}", C), collection_remover(C, "${singularToPlural(key)}", Remover)`)
if (!removers) removers = []
let removers = await this.#perspective.infer(
`subject_class("${this.#subjectClass}", C), collection_remover(C, "${singularToPlural(key)}", Remover)`
);
if (!removers) removers = [];

if (removers.length > 0) {
const actions = eval(removers[0].Remover)
const actions = eval(removers[0].Remover);
if (value) {
if (Array.isArray(value)) {
await Promise.all(value.map(v => this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value: v }])))
await Promise.all(
value.map((v) =>
this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value: v }])
)
);
} else {
await this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value }])
await this.#perspective.executeAction(actions, this.#baseExpression, [{ name: "value", value }]);
}
}
}
Expand All @@ -118,16 +143,16 @@ export class SubjectEntity {
/**
* Save the subject entity.
* This method will create a new subject with the base expression and add a new link from the source to the base expression with the predicate "ad4m://has_child".
*
*
* If a property has an action, it will perform the action (Only for collections).
* If a property is an array and is not empty, it will set the collection.
* If a property is not undefined, not null, and not an empty string, it will set the property.
*
*
*
*
* @throws Will throw an error if the subject entity cannot be converted to a subject class, or if the subject cannot be created, or if the link cannot be added, or if the subject entity cannot be updated.
*/
async save() {
this.#subjectClass = await this.#perspective.stringOrTemplateObjectToSubjectClass(this)
this.#subjectClass = await this.#perspective.stringOrTemplateObjectToSubjectClass(this);

await this.#perspective.createSubject(this, this.#baseExpression);

Expand All @@ -139,43 +164,43 @@ export class SubjectEntity {
})
);

await this.update()
await this.update();
}

/**
* Update the subject entity.
*
*
* It will iterate over the properties of the subject entity.
*
*
* If a property has an action, it will perform the action (Only for collections).
* If a property is an array and is not empty, it will set the collection.
* If a property is not undefined, not null, and not an empty string, it will set the property.
*
*
* @throws Will throw an error if the subject entity cannot be converted to a subject class, or if a property cannot be set, or if a collection cannot be set, or if the data of the subject entity cannot be gotten.
*/
async update() {
this.#subjectClass = await this.#perspective.stringOrTemplateObjectToSubjectClass(this)
this.#subjectClass = await this.#perspective.stringOrTemplateObjectToSubjectClass(this);

const entries = Object.entries(this);

for (const [key, value] of entries) {
if (value !== undefined && value !== null) {
if (value?.action) {
switch (value.action) {
case 'setter':
await this.setCollectionSetter(key, value.value)
case "setter":
await this.setCollectionSetter(key, value.value);
break;
case "adder":
await this.setCollectionAdder(key, value.value)
await this.setCollectionAdder(key, value.value);
break;
case 'remover':
await this.setCollectionRemover(key, value.value)
case "remover":
await this.setCollectionRemover(key, value.value);
default:
await this.setCollectionSetter(key, value.value)
await this.setCollectionSetter(key, value.value);
break;
}
} else if (Array.isArray(value) && value.length > 0) {
await this.setCollectionSetter(key, value)
await this.setCollectionSetter(key, value);
} else if (value !== undefined && value !== null && value !== "") {
await this.setProperty(key, value);
}
Expand All @@ -187,22 +212,21 @@ export class SubjectEntity {

/**
* Get the subject entity with all the properties & collection populated.
*
*
* @returns The subject entity.
*
*
* @throws Will throw an error if the subject entity cannot be converted to a subject class, or if the data of the subject entity cannot be gotten.
*/
async get() {
this.#subjectClass = await this.#perspective.stringOrTemplateObjectToSubjectClass(this)
this.#subjectClass = await this.#perspective.stringOrTemplateObjectToSubjectClass(this);

return await this.getData()
return await this.getData();
}


/**
* Delete the subject entity.
* This method will remove the subject from the perspective.
*
*
* @throws Will throw an error if the subject entity cannot be removed.
*/
async delete() {
Expand All @@ -211,65 +235,69 @@ export class SubjectEntity {

/**
* Get all the subject entities of the subject class.
*
*
* NOTE: this is a static method and should be called on the class itself.
*
*
* @param perspective - The perspective that the subject belongs to.
*
*
* @returns The subject entities.
*
*
* @throws Will throw an error if the subject entity cannot be converted to a subject class, or if the subject proxies cannot be gotten.
*/
static async all(perspective: PerspectiveProxy) {
let subjectClass = await perspective.stringOrTemplateObjectToSubjectClass(this)
const proxies = await perspective.getAllSubjectProxies(subjectClass)
let subjectClass = await perspective.stringOrTemplateObjectToSubjectClass(this);
const proxies = await perspective.getAllSubjectProxies(subjectClass);

const instances = []
const instances = [];

if (proxies) {
for (const proxy of proxies) {
// @ts-ignore
const instance = new this(perspective, proxy.X)
instances.push(await instance.get())
const instance = new this(perspective, proxy.X);
instances.push(await instance.get());
}

return instances;
}

return []
return [];
}

/**
* Query the subject entities of the subject class.
*
*
* NOTE: this is a static method and should be called on the class itself.
*
*
* @param perspective - The perspective that the subject belongs to.
* @param query - The query of the subject entities.
*
*
* @returns The subject entities.
*
*
* @throws Will throw an error if the subject entity cannot be converted to a subject class, or if the query cannot be inferred, or if the data of the subject entities cannot be gotten.
*/
static async query(perspective: PerspectiveProxy, query?: SubjectEntityQueryParam) {
const source = query?.source || "ad4m://self";
let subjectClass = await perspective.stringOrTemplateObjectToSubjectClass(this)
let subjectClass = await perspective.stringOrTemplateObjectToSubjectClass(this);

let res = [];

if (query) {
try {
const queryResponse = (await perspective.infer(`findall([Timestamp, Base], (subject_class("${subjectClass}", C), instance(C, Base), link("${source}", Predicate, Base, Timestamp, Author)), AllData), sort(AllData, SortedData), length(SortedData, DataLength).`))[0]
const queryResponse = (
await perspective.infer(
`findall([Timestamp, Base], (subject_class("${subjectClass}", C), instance(C, Base), link("${source}", Predicate, Base, Timestamp, Author)), AllData), sort(AllData, SortedData), length(SortedData, DataLength).`
)
)[0];

if (queryResponse.DataLength >= query.size) {
const mainQuery = `findall([Timestamp, Base], (subject_class("${subjectClass}", C), instance(C, Base), link("${source}", Predicate, Base, Timestamp, Author)), AllData), sort(AllData, SortedData), reverse(SortedData, ReverseSortedData), paginate(ReverseSortedData, ${query.page}, ${query.size}, PageData).`
const mainQuery = `findall([Timestamp, Base], (subject_class("${subjectClass}", C), instance(C, Base), link("${source}", Predicate, Base, Timestamp, Author)), AllData), sort(AllData, SortedData), reverse(SortedData, ReverseSortedData), paginate(ReverseSortedData, ${query.page}, ${query.size}, PageData).`;

res = await perspective.infer(mainQuery);

res = res[0].PageData.map(r => ({
res = res[0].PageData.map((r) => ({
Base: r[1],
Timestamp: r[0]
}))
Timestamp: r[0],
}));
} else {
res = await perspective.infer(
`subject_class("${subjectClass}", C), instance(C, Base), triple("${source}", Predicate, Base).`
Expand All @@ -288,7 +316,7 @@ export class SubjectEntity {

const data = await Promise.all(
res.map(async (result) => {
const instance = new this(perspective, result.Base)
const instance = new this(perspective, result.Base);

return await instance.get();
})
Expand All @@ -298,10 +326,12 @@ export class SubjectEntity {
}
}

export type SubjectArray<T> = T[] | {
action: 'setter' | 'adder' | 'remover',
value: T[]
}
export type SubjectArray<T> =
| T[]
| {
action: "setter" | "adder" | "remover";
value: T[];
};

export type SubjectEntityQueryParam = {
// The source of the query.
Expand All @@ -312,4 +342,4 @@ export type SubjectEntityQueryParam = {

// The page of the query.
page?: number;
}
};
Loading