Skip to content

Feat/schema improvements #1060

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

Merged
merged 28 commits into from
Apr 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
347de21
chore: commit wip with working serverside traverse overhaul
ubik2 Apr 11, 2025
f5d6363
chore: finished updating tests
ubik2 Apr 11, 2025
561439f
chore: commit wip while debugging lack of schema
ubik2 Apr 11, 2025
5691b25
fix: wrong charm title (#1023)
Gozala Apr 14, 2025
0f1dc09
add gemini-flash-lite (#1035)
anotherjesse Apr 14, 2025
4a40833
chore: Do not attempt to serve static directories (#1037)
jsantell Apr 14, 2025
de78fcd
Only re-render CharmList when connection status actually changes (#1038)
bfollington Apr 14, 2025
5efea69
track loads differently based on their schema
ubik2 Apr 14, 2025
abd6b22
Llm feedback (#1036)
jakedahn Apr 14, 2025
bad72ce
Merge branch 'main' into feat/schema-improvements
ubik2 Apr 14, 2025
44fc9ec
fix schema query with commit+json
ubik2 Apr 15, 2025
2aa7527
chore: commit wip
ubik2 Apr 15, 2025
42fca15
chore: env variable to toggle schema sub
ubik2 Apr 15, 2025
43eff08
changed to have subscribing queries not delete themselves with the ta…
ubik2 Apr 16, 2025
79f01df
Merge branch 'main' into feat/schema-improvements
ubik2 Apr 16, 2025
4027d64
we should be able to inspect both the graph/query and the subscribe f…
ubik2 Apr 16, 2025
3775f99
reverted incomplete change to docIsSyncing that caused me to stall on…
ubik2 Apr 16, 2025
22a08cd
fix blunder with pulling value from fact
ubik2 Apr 17, 2025
5b578a7
change todo comment for linter
ubik2 Apr 17, 2025
360dceb
remove incorrect comment in test
ubik2 Apr 17, 2025
84ac2cb
fix the other half of the value blunder
ubik2 Apr 17, 2025
8e3a50a
Merge branch 'main' into feat/schema-improvements
ubik2 Apr 17, 2025
9e9fa24
Remove unused import
ubik2 Apr 17, 2025
a131ea4
Added start-ci task for running integration tests that makes Broadcas…
ubik2 Apr 17, 2025
feacb68
Change cache to only use the BroadcastChannel if it's available.
ubik2 Apr 17, 2025
5ea88b3
better import of isObj
ubik2 Apr 17, 2025
5313722
just inline the isObj test
ubik2 Apr 17, 2025
4391102
Remove the BroadcastChannel, since we're not really using it.
ubik2 Apr 17, 2025
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
75 changes: 61 additions & 14 deletions memory/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ConsumerResultFor,
DID,
Entity,
Fact,
InferOf,
Invocation,
InvocationURL,
Expand All @@ -26,6 +27,9 @@ import {
QueryError,
Reference,
Result,
Revision,
SchemaContext,
SchemaPathSelector,
SchemaQuery,
SchemaSelector,
Seconds,
Expand All @@ -41,15 +45,14 @@ import {
import { refer } from "./reference.ts";
import * as Socket from "./socket.ts";
import * as ChangesBuilder from "./changes.ts";
import * as Fact from "./fact.ts";
import * as FactModule from "./fact.ts";
import * as Access from "./access.ts";
import * as Subscription from "./subscription.ts";
import { toStringStream } from "./ucan.ts";
import { fromStringStream } from "./receipt.ts";
import * as Settings from "./settings.ts";
export * from "./interface.ts";
import { the as commitType, toRevision } from "./commit.ts";
export { ChangesBuilder };
import { toRevision } from "./commit.ts";

export const connect = ({
address,
Expand Down Expand Up @@ -145,17 +148,27 @@ class MemoryConsumerSession<
const id = command.of;
if (command.the === "task/return") {
const invocation = this.invocations.get(id);
this.invocations.delete(id);
// TODO(@ubik2) this is really gross.
if (
invocation === undefined || !("args" in invocation) ||
invocation.args === null || !(typeof invocation.args === "object") ||
!("subscribe" in invocation.args) || !invocation.args.subscribe
) {
this.invocations.delete(id);
}
invocation?.return(command.is as NonNullable<unknown>);
} // If it is an effect it can be for one specific subscription, yet we may
// have other subscriptions that will be affected. There for we simply
// pass effect to each one and they can detect if it concerns them.
// ℹ️ We could optimize this in the future and try indexing subscriptions
// so we don't have to broadcast to all.
// have other subscriptions that will be affected.
// We can't just send one message over, since the client needs to know
// about which extra objects are needed for that specific subscription.
// There's a chance we'll send the same object over more than once because
// of this (in particular, this is almost guaranteed by the cache that
// maintains a subscription to every object in the cache).
// For now, I think this is the best approach, but we can use the since
// fields to remove these later.
else if (command.the === "task/effect") {
for (const [, invocation] of this.invocations) {
invocation.perform(command.is);
}
const invocation = this.invocations.get(id);
invocation?.perform(command.is);
}
}

Expand Down Expand Up @@ -268,7 +281,7 @@ class MemorySpaceConsumerSession<Space extends MemorySpace>
args: source,
});
return QueryView.create(this.session, query);
} else {
} else { // selectSchema
const query = this.session.invoke({
cmd: "/memory/graph/query" as const,
sub: this.space,
Expand Down Expand Up @@ -394,6 +407,11 @@ class QueryView<
Result<QueryView<Space, MemoryProtocol>, QueryError | ConnectionError>
>,
) {
invocation.perform = (
effect:
| ConsumerEffectFor<"/memory/query", MemoryProtocol>
| ConsumerEffectFor<"/memory/graph/query", MemoryProtocol>,
) => this.perform(effect as any);
this.selection = { [this.space]: {} } as Selection<InferOf<Protocol>>;
this.selector = ("select" in this.invocation.args)
? (this.invocation.args as { select?: Selector }).select as Selector
Expand All @@ -405,6 +423,12 @@ class QueryView<
this.selection = selection;
}

perform(effect: Selection<Space>) {
const differential = { ...effect[this.space] } as Selection<Space>;
this.integrate(differential);
return { ok: effect };
}

then<T, X>(
onResolve: (
value: Result<
Expand Down Expand Up @@ -432,8 +456,14 @@ class QueryView<
}
}

get facts() {
return [...Fact.iterate(this.selection[this.space])];
get facts(): Revision<Fact>[] {
return [...FactModule.iterate(this.selection[this.space])];
}

// Get the facts returned by the query, together with the associated
// schema context used to query
get schemaFacts(): [Revision<Fact>, SchemaContext | undefined][] {
return this.facts.map((fact) => [fact, this.getSchema(fact)]);
}

subscribe() {
Expand All @@ -447,6 +477,7 @@ class QueryView<
// Since we already have these results, we don't want to re-fetch them, so
// make a QueryView available for subscriptions to use that lets us skip
// that step when we subscribe.
// TODO(@ubik2) Remove this code when we remove the rest of remote.ts
includedQueryView(): QueryView<MemorySpace, MemoryProtocol> | undefined {
const factSelection = this.selection[this.space];
const subSelector: Selector = {};
Expand Down Expand Up @@ -484,6 +515,22 @@ class QueryView<
subQueryView.promise = Promise.resolve({ ok: subQueryView });
return subQueryView;
}

// Get the schema context used to fetch the specified fact.
// If the fact was included from another fact, it will not have a schemaContext.
getSchema(fact: Revision<Fact>): SchemaContext | undefined {
const factSelector = this.selector;
const attributes = factSelector[fact.of];
if (attributes !== undefined) {
const causes = attributes[fact.the] as Record<Cause, SchemaPathSelector>;
if (causes !== undefined) {
for (const [_cause, selector] of Object.entries(causes)) {
return selector.schemaContext;
}
}
}
return undefined;
}
}

class QuerySubscriptionInvocation<
Expand Down
24 changes: 18 additions & 6 deletions memory/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Reference } from "merkle-reference";
import { refer, type Reference } from "merkle-reference";
import { JSONSchema } from "../builder/src/types.ts";

export type { Reference };
Expand Down Expand Up @@ -144,7 +144,8 @@ export type Protocol<Space extends MemorySpace = MemorySpace> = {
>;
query: {
(query: { select: Selector; since?: number }): Task<
Result<Selection<Space>, AuthorizationError | QueryError>
Result<Selection<Space>, AuthorizationError | QueryError>,
Selection<Space>
>;
subscribe(
source: Subscribe<Space>["args"],
Expand All @@ -158,9 +159,14 @@ export type Protocol<Space extends MemorySpace = MemorySpace> = {
};
graph: {
query(
schemaQuery: { selectSchema: SchemaSelector; since?: number },
schemaQuery: {
selectSchema: SchemaSelector;
since?: number;
subscribe?: boolean;
},
): Task<
Result<Selection<Space>, AuthorizationError | QueryError>
Result<Selection<Space>, AuthorizationError | QueryError>,
Selection<Space>
>;
};
};
Expand Down Expand Up @@ -714,7 +720,7 @@ export type Unsubscribe<Space extends MemorySpace = MemorySpace> = Invocation<
export type SchemaQuery<Space extends MemorySpace = MemorySpace> = Invocation<
"/memory/graph/query",
Space,
{ selectSchema: SchemaSelector; since?: number }
{ selectSchema: SchemaSelector; since?: number; subscribe?: boolean }
>;

// A normal Selector looks like this (with _ as wildcard cause):
Expand Down Expand Up @@ -750,7 +756,7 @@ export type SchemaSelector = Select<

export type SchemaPathSelector = {
path: string[];
schemaContext: SchemaContext;
schemaContext?: SchemaContext;
};

// This is a schema, together with its rootSchema for resolving $ref entries
Expand Down Expand Up @@ -964,3 +970,9 @@ export type Variant<U extends Record<string, unknown>> = {
[K in Key]: U[Key];
};
}[keyof U];

// This will match every doc reachable by the specified set of documents
export const SchemaAll: SchemaContext = { schema: true, rootSchema: true };

// This is equivalent to a standard query, and will only match the specified documents
export const SchemaNone: SchemaContext = { schema: false, rootSchema: false };
Loading