From ec27ae3a371f0b997e018866941281b0cd1ed64a Mon Sep 17 00:00:00 2001 From: Daniel Nagy <1622446+daniel-nagy@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:14:08 -0500 Subject: [PATCH 1/5] beta 6 --- .tool-versions | 2 +- bun.lockb | Bin 190400 -> 190664 bytes packages/browser/web-test-runner.config.js | 4 +-- packages/core/package.json | 2 +- packages/core/src/ClientAgent.ts | 2 +- packages/core/src/Fiber.ts | 17 +++++++++- packages/core/src/Metadata.ts | 4 +-- packages/core/src/PubSub.ts | 13 ++++++++ packages/core/src/Session.test.ts | 37 +++++++++++++++++---- 9 files changed, 66 insertions(+), 15 deletions(-) diff --git a/.tool-versions b/.tool-versions index 2263cc3..199cf09 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -bun 1.0.17 +bun 1.1.21 diff --git a/bun.lockb b/bun.lockb index 24424dc7bce05d9a6f84d6829042d2c12fd86eb7..ef3a1bc76b0043da0cbed9bf7319b629551bb51c 100755 GIT binary patch delta 5235 zcmYkA4UiM%8OL|`?jmqD$7hfa!`%VlFuW9Gr$qF@Q)EtFaE4*1?9W zGiI>GRB)QLI7US$+ObB3np32aibkv&P%wUB1n~lb1gbTBsG-mEf8KW7x%c;b_qUtP zZn6iLY~NM8^S#;!%*G>~$DVH<9=fk)OmpLvCkEBsy=>3P=byC=|6$_Pqxo;gwsahR zq4Tl2nq6}>t#-?Ib*t<8ub;H=e%FiBFM8tbhwggxmiJd!^Oor|w`?CZc(txSIACV; zhWb7O?%AMe7i(JgU`^A`rD1q*8(>g7P0-0_*js-0@a=Vd|DPYc zbL)~?+Nf)lx(b<}V-6kOd}RSK3Kl^3HZC!+2zvg(B@UKAZ-z?(EQ7vRxg@~~=-dV@;@%!6i@OB5`C?l-x_z#{0`$t4b! zK<`^z5?~qh{fkQytbqQvxuihbJ3J47Y0&;Immrt{9XT!`Fbg_&aS4Mt(6yUO1k8ix zdt9Pm0d&95B?cBj&mJyuumpPda!G(?(DwnCBv=9cc`hl?_94#$U>dZ4#3cx3K*z^i zLSPnje!?XT=0Mk{Tq0l|H1~0df(6k18J8GX1U&^Vaj*n>Kj)GF%b;&Rmn2vL{RgWos<&p#|p#N(wDbRL|=K(Mc+Dlx5U z%!1DExrD(S==y<61k8ixk6fZ)0d!Zm#K0oxIm;yumY9F`>dXXKW?DWSN{T8Xzk!$% z*{ZM>5T!--YQ&%@BXZOrhD2GB(}oxpOuoe)dMRqS@P?QllE=LTBvLfddh+$Dq4lyaJi2Mr?QzF|hu@(@eMfOFA zK~YBJxE?Vi%8Hx;#IPtQaxF%Ti1H$H31U=K5V@Bk#zaMt=LW>Ms3h_(Lrjpa`^hJk zWBk!ZE!9uVxaFhAH@$3Ibl>8pYS*Ya)B3h9tr^mN+51(Sr(S;R0^jCKzPVy;*T^pC zT)Xe?_bv&0VhcC!8~yu%cW-!S{@Uim`Sb_Fjr!18FTAzm(B2hGX55+mZSyQ6`@r0- z^IzCIW6B?v-@J6);EnrMJ^7k@_sTV%XI9)+2gIUvsrO=RLR%P3uqRKbz(?=H_XH8;_z zHPG63ROv@+rm;w!ZdtVn)O6MO4dwkN(x_66-%xHQjRu-Dl25BPiRMr9Mq19O)rmg;x5g$XCP%?4YrkC$v;ka9V!%U+wo94Vff9=7Wr_=BZ;~6crWIP|gQ?#_4vMMFrWz`?o*Y!MqT(8!R(?-wc z6MCq|IAd6iW&Ns)H_@(tp)rfbVj6tsEu-V`E47UV->HXaWa)T(S8t>760JX_Td}e} zUhlD9EbCLc(v+T~k)pxZw}ZwTG#1mLme9DC27iOIh1%cMHolfgG<-DX(zuky67_pM zyV|hMmh`bbohS8uwZo6oLrOHxqcN05H@}{=ROa<~tBkhU?M-^1wbg7h+q6aN>DPkx zIM#CZVEY%JK0;eM%vO`$$=m1w^|WQ@JGmpnI|F8u_3T_@7(di)PT?K;&^J(hX@}Kc z-}LU$>6Ya?oBoyQespa2gywsf-#}a1&B^?jk(bJo z|L-d@LyvTr?PhC>HDIPOXhfu~#cXXeI|g;)+p+Gxw@=F+nYZy3+Rb{m(`e{-#e8{5 z)^=HEdW{i1gXS5_^?omPNqxH5bI*L^u?ztb14Uz`HKb^C)Y3~g z@!;rbAN+04QEUGn_4@HQEMK*hU(o+meye`E>{bWaf5>{d*Kk=oi$?!`O^4;k9fxrN K){jMF$A1Au_%Raz delta 4837 zcmYkA3y@Rg8HUd}u#sz)YY9qA``HJ!`?Oz+4zBuju?VG=NZ~4YwA6;xrTcAF@bysDN6=e!-J=U)s zFnC>6yUu@HrzqnUrGab3`n6TVD5`t5PW)hVdDk}7?r40tSl#;O2d|a4e~{O|zHNSc z+NUa|@{(#)u2HA2uh~9JRn~{M&nD(T=MJ7nz&z;sj7t`G?^V|m}LF?yS{9p>S?covt)1ZAXmmrt{9s9V1z$~bzxrD(S=-kgG0_H*20WMLn z0J{IfB?cBj&wshZ!4l~Gf=dFl9OStVOoG-!T>M}Pv}L#iz%*z-%q0kBK*yI{LSPou zk8lZtIna5OO9ae=uCKU6!2;<1noA5Uf}U@<#K98i&2mYAmSa5kfl1K%Ef+tS0&T~+ z1i&ZiDb!5rv3%_RcnL067T6fA)5@3_RkBIr58B@UKA?^!Mh z&~lFFJ}?Pd&vWsEDbV&kmjIXs?M+;QUh4_^PuZKmnc|ZZg8v27+7Q)9u)d4XoDu~=2 z5o4mF$kPciE-Hzivixn%MC@Hd*Bl<-tk*y12K$I5QyCMcf8IhwKVn~z~>D>{- zqMXRt12H1Xi(EYsqoRVyZ9|NSiXu-h#JH#=@;-`~5LtR-W}((4ez zqMXQCj~EfE z7TL!k21OZ>!-W_UWkvc^h+$Dqw{#Hgqs zazBq46BR`s4`N(Y5_zW}CPbF0SouUrk#!oPUz8HrrXvPKX_0*fVo;P3Ic6e;L|KtO z3o$IpiJV@i8@&Z;qQBq``i|7}n zM79?Z1ERFZ?n4ZUG9t%3#E>W}(&rDcP4O-omAc{6^vX;#R2_a76l4%zYX{+UMg zq1p8V6(hb-olPIoh8Yd3)V7Vo7pZGjX*UXu+b*gBb*0C=<*&#*?LvbdCAP24)|FPT znxEdjFlu6fs2%{GXdYPJ@$IjFTU+cmQdrq-Y4{7qgr+Yp-b%~X+% zfd#c{vJ&b&H5cs}lN8vo`AZMV&))10rzuX4w1 zBWPZ4o_p79HPm!zPb&A!IFjOIihRP4)Odd_4VQWHee<{~ejjM7YBf$=RUJm#7WEzV zcH`<6^%1r4&)3x7lxgp)hSsW1svb{A&ZDuD249jNaWjoHoz9PWhhl_V)uF~Gt!kZd zqgCxOv@5;XRytxI4crW^Jv64!m_TD{EA60dY@5b0w^XfDpI)@)8{!AX^QGofC`@hi zw$*AU4``?QMvl~L^;%`_T0Gl^D>e7e*PlE2SK2dLAF0Hl>Z{vXJ z+9I{Xmg!PAHZ`uBp>0~%rbe$dRG(I5T;Hr!s-unFA8OUcyLoL$hciubXiJmv_h!w} zS8Oe|_2Lyd3ae9 = {}; const meta: Metadata.t = { - address: this.serverAddress, + clientAgentId: this.id, objectPath: path }; diff --git a/packages/core/src/Fiber.ts b/packages/core/src/Fiber.ts index 1cd69b6..cd1caac 100644 --- a/packages/core/src/Fiber.ts +++ b/packages/core/src/Fiber.ts @@ -2,6 +2,11 @@ import * as BehaviorSubject from "./BehaviorSubject.js"; export { Fiber as t }; +/** + * Keeps track of all active fibers. + */ +const table = new Map(); + /** * A fiber's state. */ @@ -31,7 +36,9 @@ export class Fiber { * A unique identifier for this fiber. */ public readonly id: string = crypto.randomUUID() - ) {} + ) { + table.set(id, this); + } /** * Terminates the fiber. The fiber's state will transition to `Terminated` and @@ -40,6 +47,7 @@ export class Fiber { terminate() { this.#state.next(State.Terminated); this.#state.complete(); + table.delete(this.id); } [Symbol.dispose]() { @@ -47,6 +55,13 @@ export class Fiber { } } +/** + * Get a reference to a Fiber from its id. + */ +export const get = (id: string): T | null => { + return (table.get(id) as T) ?? null; +}; + /** * Creates a new `Fiber`. */ diff --git a/packages/core/src/Metadata.ts b/packages/core/src/Metadata.ts index c0dc36d..1445d9d 100644 --- a/packages/core/src/Metadata.ts +++ b/packages/core/src/Metadata.ts @@ -9,9 +9,9 @@ export const symbol = Symbol.for("metadata"); */ export type Metadata = { /** - * The address of the server that provides the value. + * The id of the client agent managing this proxy. */ - address: string; + clientAgentId: string; /** * The path to the value in the original object from the dereferenced value. */ diff --git a/packages/core/src/PubSub.ts b/packages/core/src/PubSub.ts index f1c2e85..5d910b4 100644 --- a/packages/core/src/PubSub.ts +++ b/packages/core/src/PubSub.ts @@ -1,3 +1,5 @@ +import * as Fiber from "./Fiber.js"; +import * as Metadata from "./Metadata.js"; import * as Observable from "./Observable/index.js"; /** @@ -36,8 +38,19 @@ export function from(observable: Observable.ObservableLike): PubSub { subscribe: async ( observer: AsyncObserver | ((value: T) => Promise) ) => { + const metadata = Metadata.get(observer); const subscription = observable.subscribe(observer); + if (metadata) { + Fiber.get(metadata.clientAgentId)?.stateChange.subscribe((state) => { + switch (state) { + case Fiber.State.Terminated: + console.log("terminated"); + subscription.unsubscribe(); + } + }); + } + return { unsubscribe: async () => subscription.unsubscribe() }; diff --git a/packages/core/src/Session.test.ts b/packages/core/src/Session.test.ts index 3a76a15..a4b196b 100644 --- a/packages/core/src/Session.test.ts +++ b/packages/core/src/Session.test.ts @@ -1,6 +1,7 @@ import { afterEach, describe, expect, test } from "bun:test"; import { spy, spyOn } from "tinyspy"; +import * as BehaviorSubject from "./BehaviorSubject.js"; import * as Cache from "./Cache.js"; import * as Fiber from "./Fiber.js"; import * as Injector from "./Injector.js"; @@ -9,9 +10,12 @@ import * as Message from "./Message.js"; import * as Metadata from "./Metadata.js"; import * as Observable from "./Observable/index.js"; import * as Proxy from "./Proxy.js"; +import * as PubSub from "./PubSub.js"; import * as Session from "./Session.js"; import * as Subprotocol from "./Subprotocol.js"; +const UUID = expect.stringMatching(/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/) as string; + afterEach(() => { Session.rootSupervisor.tasks.forEach((task) => task.terminate()); }); @@ -256,12 +260,12 @@ describe("proxied objects", () => { const { proxy, dispose } = expose({ bar: async () => {} }); expect(Metadata.get(proxy)).toEqual({ - address: "", + clientAgentId: UUID, objectPath: [] }); expect(Metadata.get(proxy.bar)).toEqual({ - address: "", + clientAgentId: UUID, objectPath: ["bar"] }); @@ -284,9 +288,7 @@ describe("proxied objects", () => { const childProxy = await proxy(); expect(Metadata.get(childProxy)).toEqual({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - address: expect.stringMatching(/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/), + clientAgentId: UUID, objectPath: [] }); @@ -295,12 +297,33 @@ describe("proxied objects", () => { }); }); +describe("pub/sub", () => { + test("A pub/sub is unsubscribed when the session is terminated", async () => { + const counter = BehaviorSubject.of(0); + const unsubscribe = spy(); + + spyOn(counter, "subscribe", () => ({ unsubscribe })); + + const { dispose, proxy, server } = expose({ + Chat: { counter: PubSub.from(counter) } + }); + + proxy.Chat.counter.subscribe(async () => {}); + + await scheduleTask(); + server.terminate(); + + expect(unsubscribe.callCount).toBe(1); + dispose(); + }); +}); + describe("reflection", () => { test("the root proxy", () => { const { proxy, dispose } = expose(async () => {}); expect(Metadata.get(proxy)).toEqual({ - address: "", + clientAgentId: UUID, objectPath: [] }); @@ -311,7 +334,7 @@ describe("reflection", () => { const { proxy, dispose } = expose({ a: async () => {} }); expect(Metadata.get(proxy.a)).toEqual({ - address: "", + clientAgentId: UUID, objectPath: ["a"] }); From 477c0c784287c7ac07f198d054be1eb73738f9b8 Mon Sep 17 00:00:00 2001 From: Daniel Nagy <1622446+daniel-nagy@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:32:03 -0500 Subject: [PATCH 2/5] update README --- packages/core/README.md | 4 ++-- packages/core/src/PubSub.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/README.md b/packages/core/README.md index eb2966d..3d61128 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -784,9 +784,9 @@ The Metadata module allows information to be extracted from a proxy. ```ts type Metadata = { /** - * The address of the server that provides the value. + * The id of the client agent managing this proxy. */ - address: string; + clientAgentId: string; /** * The path to the value in the original object. */ diff --git a/packages/core/src/PubSub.ts b/packages/core/src/PubSub.ts index 5d910b4..6b223b3 100644 --- a/packages/core/src/PubSub.ts +++ b/packages/core/src/PubSub.ts @@ -45,7 +45,6 @@ export function from(observable: Observable.ObservableLike): PubSub { Fiber.get(metadata.clientAgentId)?.stateChange.subscribe((state) => { switch (state) { case Fiber.State.Terminated: - console.log("terminated"); subscription.unsubscribe(); } }); From 00892670a222049ab2db7db8ad67a1eb79174f93 Mon Sep 17 00:00:00 2001 From: Daniel Nagy <1622446+daniel-nagy@users.noreply.github.com> Date: Thu, 1 Aug 2024 12:11:22 -0500 Subject: [PATCH 3/5] add UUID fallback --- packages/core/src/Fiber.ts | 3 ++- packages/core/src/Message.ts | 9 +++++---- packages/core/src/PubSub.ts | 20 +++++++++++--------- packages/core/src/Session.ts | 3 ++- packages/core/src/UUID.test.ts | 9 +++++++++ packages/core/src/UUID.ts | 29 +++++++++++++++++++++++++++++ 6 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 packages/core/src/UUID.test.ts create mode 100644 packages/core/src/UUID.ts diff --git a/packages/core/src/Fiber.ts b/packages/core/src/Fiber.ts index cd1caac..ac42d11 100644 --- a/packages/core/src/Fiber.ts +++ b/packages/core/src/Fiber.ts @@ -1,4 +1,5 @@ import * as BehaviorSubject from "./BehaviorSubject.js"; +import * as UUID from "./UUID.js"; export { Fiber as t }; @@ -35,7 +36,7 @@ export class Fiber { /** * A unique identifier for this fiber. */ - public readonly id: string = crypto.randomUUID() + public readonly id: string = UUID.v4() ) { table.set(id, this); } diff --git a/packages/core/src/Message.ts b/packages/core/src/Message.ts index 6e007bc..cc96f59 100644 --- a/packages/core/src/Message.ts +++ b/packages/core/src/Message.ts @@ -1,4 +1,5 @@ import * as JsObject from "./JsObject.js"; +import * as UUID from "./UUID.js"; /** * Flattens an intersection type into a single type. @@ -89,7 +90,7 @@ export type CallFunction = FlattenIntersection< export const CallFunction = ({ address, args, - id = crypto.randomUUID(), + id = UUID.v4(), noReply = false, path }: { @@ -122,7 +123,7 @@ export type Error = FlattenIntersection< export const Error = ({ address, error, - id = crypto.randomUUID() + id = UUID.v4() }: { address: string; error: T; @@ -147,7 +148,7 @@ export type GarbageCollect = FlattenIntersection< */ export const GarbageCollect = ({ address, - id = crypto.randomUUID() + id = UUID.v4() }: { address: string; id?: string; @@ -171,7 +172,7 @@ export type SetValue = FlattenIntersection< */ export const SetValue = ({ address, - id = crypto.randomUUID(), + id = UUID.v4(), value }: { address: string; diff --git a/packages/core/src/PubSub.ts b/packages/core/src/PubSub.ts index 6b223b3..61d68fd 100644 --- a/packages/core/src/PubSub.ts +++ b/packages/core/src/PubSub.ts @@ -39,19 +39,21 @@ export function from(observable: Observable.ObservableLike): PubSub { observer: AsyncObserver | ((value: T) => Promise) ) => { const metadata = Metadata.get(observer); + const agent = metadata && Fiber.get(metadata.clientAgentId); const subscription = observable.subscribe(observer); - if (metadata) { - Fiber.get(metadata.clientAgentId)?.stateChange.subscribe((state) => { - switch (state) { - case Fiber.State.Terminated: - subscription.unsubscribe(); - } - }); - } + const innerSubscription = agent?.stateChange.subscribe((state) => { + switch (state) { + case Fiber.State.Terminated: + subscription.unsubscribe(); + } + }); return { - unsubscribe: async () => subscription.unsubscribe() + unsubscribe: async () => { + innerSubscription?.unsubscribe(); + subscription.unsubscribe(); + } }; } }; diff --git a/packages/core/src/Session.ts b/packages/core/src/Session.ts index 9838ece..076e475 100644 --- a/packages/core/src/Session.ts +++ b/packages/core/src/Session.ts @@ -11,6 +11,7 @@ import * as ServerAgent from "./ServerAgent.js"; import * as Subject from "./Subject.js"; import * as Subprotocol from "./Subprotocol.js"; import * as Supervisor from "./Supervisor.js"; +import * as UUID from "./UUID.js"; export { Session as t }; @@ -113,7 +114,7 @@ export abstract class Session< protected createServer( this: Session, provide: unknown, - address: string = crypto.randomUUID() + address: string = UUID.v4() ): ServerAgent.t { const serverAgent = ServerAgent.init({ address, diff --git a/packages/core/src/UUID.test.ts b/packages/core/src/UUID.test.ts new file mode 100644 index 0000000..516594a --- /dev/null +++ b/packages/core/src/UUID.test.ts @@ -0,0 +1,9 @@ +import { expect, test } from "bun:test"; + +import * as UUID from "./UUID.js"; + +test("generating a v4 UUID", () => { + expect(UUID.v4()).toEqual( + expect.stringMatching(/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/) + ); +}); diff --git a/packages/core/src/UUID.ts b/packages/core/src/UUID.ts new file mode 100644 index 0000000..b0c07f8 --- /dev/null +++ b/packages/core/src/UUID.ts @@ -0,0 +1,29 @@ +function hex(bits: number) { + if (bits > 53) throw new Error("bits must be less than or equal to 53"); + + return Math.floor(Math.random() * (2 ** bits - 1)) + .toString(16) + .padStart(Math.ceil(bits / 4), "0"); +} + +/** + * Generates a v4 UUID. This implementation generates exactly 122 bits of random + * data and concatenates the version and variant. + * + * @see https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-4 + */ +function v4Fallback(): string { + const a = hex(48); + const b = hex(12); + const c = `${hex(31)}${hex(31)}`; + + return `${a.slice(0, 8)}-${a.slice(8)}-4${b}-a${c.slice(0, 3)}-${c.slice( + 3, + 15 + )}`; +} + +export const v4 = + typeof crypto !== "undefined" && typeof crypto.randomUUID !== "undefined" + ? crypto.randomUUID.bind(crypto) + : v4Fallback; From 51d9cc380c34062c8f46e82180f23994751cac92 Mon Sep 17 00:00:00 2001 From: Daniel Nagy <1622446+daniel-nagy@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:34:43 -0500 Subject: [PATCH 4/5] bump version --- packages/browser/package.json | 2 +- packages/core/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/browser/package.json b/packages/browser/package.json index b6a9222..0753dc9 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,7 +1,7 @@ { "name": "@daniel-nagy/transporter-browser", "type": "module", - "version": "1.0.0-beta.5", + "version": "1.0.0-beta.6", "description": "Typesafe distributed computing in the browser.", "author": "Daniel Nagy <1622446+daniel-nagy@users.noreply.github.com>", "repository": "github:daniel-nagy/transporter", diff --git a/packages/core/package.json b/packages/core/package.json index 048cf18..f54a413 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@daniel-nagy/transporter", "type": "module", - "version": "1.0.0-beta.5", + "version": "1.0.0-beta.6", "description": "Typesafe distributed computing in TypeScript.", "author": "Daniel Nagy <1622446+daniel-nagy@users.noreply.github.com>", "repository": "github:daniel-nagy/transporter", From ee1fbb00ace9c730cad8b7a29861ceef7469379a Mon Sep 17 00:00:00 2001 From: Daniel Nagy <1622446+daniel-nagy@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:37:38 -0500 Subject: [PATCH 5/5] update lock file --- bun.lockb | Bin 190664 -> 191008 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index ef3a1bc76b0043da0cbed9bf7319b629551bb51c..104e45da72b274689b11fa161d96fab080102b50 100755 GIT binary patch delta 17205 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 D@(FhX delta 17039 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