Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
25 changes: 11 additions & 14 deletions renderers/web_core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions renderers/web_core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"zod-to-json-schema": "^3.25.1"
},
"dependencies": {
"rxjs": "^7.8.2",
"@preact/signals-core": "^1.13.0",
"zod": "^3.25.76"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { describe, it } from "node:test";
import * as assert from "node:assert";
import { effect } from "@preact/signals-core";
import { BASIC_FUNCTIONS } from "./basic_functions.js";
import { DataModel } from "../../state/data-model.js";
import { DataContext } from "../../rendering/data-context.js";
Expand Down Expand Up @@ -238,11 +239,16 @@ describe("BASIC_FUNCTIONS", () => {
const result = BASIC_FUNCTIONS.formatString(
{ value: "hello world" },
context,
) as import("rxjs").Observable<string>;
) as import("@preact/signals-core").Signal<string>;

result.subscribe((val) => {
assert.strictEqual(val, "hello world");
done();
let cleanup: (() => void) | undefined;
cleanup = effect(() => {
const val = result.value;
if (val) {
assert.strictEqual(val, "hello world");
if (cleanup) cleanup();
done();
}
});
});

Expand All @@ -251,32 +257,30 @@ describe("BASIC_FUNCTIONS", () => {
const result = BASIC_FUNCTIONS.formatString(
{ value: "Value: ${a}" },
context,
) as import("rxjs").Observable<string>;
) as import("@preact/signals-core").Signal<string>;

let emitCount = 0;
const sub = result.subscribe({
next: (val) => {
try {
if (emitCount === 0) {
assert.strictEqual(val, "Value: 10");
emitCount++;
// Trigger a change in the next tick to avoid uninitialized sub
setTimeout(() => {
dataModel.set("/a", 42);
}, 0);
} else if (emitCount === 1) {
assert.strictEqual(val, "Value: 42");
emitCount++;
sub.unsubscribe();
done();
}
} catch (e) {
done(e);
let cleanup: (() => void) | undefined;
cleanup = effect(() => {
const val = result.value;
try {
if (emitCount === 0) {
assert.strictEqual(val, "Value: 10");
emitCount++;
// Trigger a change in the next tick to avoid uninitialized sub
setTimeout(() => {
dataModel.set("/a", 42);
}, 0);
} else if (emitCount === 1) {
assert.strictEqual(val, "Value: 42");
emitCount++;
if (cleanup) cleanup();
done();
}
},
error: (e) => {
} catch (e) {
if (cleanup) cleanup();
done(e);
},
}
});
});

Expand All @@ -292,11 +296,16 @@ describe("BASIC_FUNCTIONS", () => {
const result = BASIC_FUNCTIONS.formatString(
{ value: "Result: ${add(a: 5, b: 7)}" },
ctxWithInvoker,
) as import("rxjs").Observable<string>;
) as import("@preact/signals-core").Signal<string>;

result.subscribe((val) => {
assert.strictEqual(val, "Result: 12");
done();
let cleanup: (() => void) | undefined;
cleanup = effect(() => {
const val = result.value;
if (val) {
assert.strictEqual(val, "Result: 12");
if (cleanup) cleanup();
done();
}
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
*/

import { ExpressionParser } from "../expressions/expression_parser.js";
import { Observable, combineLatest, of } from "rxjs";
import { map } from "rxjs/operators";
import { computed, Signal } from "@preact/signals-core";
import { FunctionImplementation } from "../../catalog/types.js";

/**
Expand Down Expand Up @@ -156,27 +155,24 @@ export const BASIC_FUNCTIONS: Record<string, FunctionImplementation> = {

if (parts.length === 0) return "";

const observables = parts.map((part) => {
// If it's a literal string (or number/boolean/etc), wrap it in 'of'
const dynamicParts = parts.map((part) => {
// If it's a literal string (or number/boolean/etc), keep it as is
if (typeof part !== "object" || part === null || Array.isArray(part)) {
return of(part);
return part;
}

// Otherwise, it's a dynamic value we need to subscribe to
return new Observable<unknown>((subscriber) => {
const sub = context.subscribeDynamicValue(part, (val) => {
subscriber.next(val);
});

// Emit the initial synchronously-resolved value
subscriber.next(sub.value);

return () => sub.unsubscribe();
});
return context.resolveSignal(part);
});

// Combine all parts and join them into a single string whenever any part changes
return combineLatest(observables).pipe(map((values) => values.join("")));
return computed(() => {
return dynamicParts
.map((p) => {
if (p instanceof Signal) {
return p.value;
}
return p;
})
.join("");
});
},

/**
Expand Down
4 changes: 2 additions & 2 deletions renderers/web_core/src/v0_9/catalog/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@

import { z } from "zod";
import { DataContext } from "../rendering/data-context.js";
import { Observable } from "rxjs";
import { Signal } from "@preact/signals-core";

/**
* A function implementation that can be registered with the evaluator or basic catalog.
*/
export type FunctionImplementation = (
args: Record<string, unknown>,
context: DataContext,
) => unknown | Observable<unknown>;
) => unknown | Signal<unknown>;

/**
* A definition of a UI component's API.
Expand Down
4 changes: 2 additions & 2 deletions renderers/web_core/src/v0_9/rendering/data-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import assert from "node:assert";
import { describe, it, beforeEach } from "node:test";
import { of } from "rxjs";
import { signal } from "@preact/signals-core";
import { DataModel } from "../state/data-model.js";
import { DataContext } from "./data-context.js";

Expand Down Expand Up @@ -172,7 +172,7 @@ describe("DataContext", () => {

it("subscribes to function call returning an observable", () => {
const fnInvoker = (name: string) => {
if (name === "obs") return of("hello");
if (name === "obs") return signal("hello");
return null;
};
const ctx = new DataContext(model, "/", fnInvoker);
Expand Down
Loading
Loading