Skip to content
Merged
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
14 changes: 7 additions & 7 deletions background-charm-service/cast-admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
import { type DID, Identity } from "@commontools/identity";
import { createAdminSession } from "@commontools/identity";
import {
BG_CELL_CAUSE,
BG_SYSTEM_SPACE_ID,
bgUpdaterCharmsSchema,
CELL_CAUSE,
SYSTEM_SPACE_ID,
} from "@commontools/utils";
import { getIdentity } from "./src/utils.ts";

const { recipePath, name, quit } = parseArgs(
const { recipePath, quit } = parseArgs(
Deno.args,
{
string: ["recipePath"],
Expand Down Expand Up @@ -46,8 +46,8 @@ storage.setRemoteStorage(new URL(toolshedUrl));
setBobbyServerUrl(toolshedUrl);

async function castRecipe() {
const spaceId = SYSTEM_SPACE_ID;
const cause = CELL_CAUSE;
const spaceId = BG_SYSTEM_SPACE_ID;
const cause = BG_CELL_CAUSE;
console.log(`Casting recipe from ${recipePath} in space ${spaceId}`);

storage.setSigner(identity);
Expand Down Expand Up @@ -79,11 +79,11 @@ async function castRecipe() {
const targetCell = getCell(
spaceId as DID,
cause,
bgUpdaterCharmsSchema.properties.charms,
bgUpdaterCharmsSchema,
);

// Ensure the cell is synced
storage.syncCell(targetCell, true);
await storage.syncCell(targetCell, true);
await storage.synced();

console.log("Getting cell...");
Expand Down
4 changes: 2 additions & 2 deletions background-charm-service/src/service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Identity } from "@commontools/identity";
import { type Cell, storage } from "@commontools/runner";
import { type BGCharmEntry, newGetFunc } from "@commontools/utils";
import { type BGCharmEntry, getBGUpdaterCharmsCell } from "@commontools/utils";
import { log } from "./utils.ts";
import { SpaceManager } from "./space-manager.ts";
import { useCancelGroup } from "@commontools/runner";
Expand All @@ -25,7 +25,7 @@ export class BackgroundCharmService {
async initialize() {
storage.setRemoteStorage(new URL(this.toolshedUrl));
storage.setSigner(this.identity);
this.charmsCell = await newGetFunc();
this.charmsCell = await getBGUpdaterCharmsCell();
await storage.syncCell(this.charmsCell, true);
await storage.synced();

Expand Down
11 changes: 9 additions & 2 deletions background-charm-service/src/space-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ export class SpaceManager {
})),
]).then((result) => {
if (!result.success) {
this.disableCharm(charmId, bg);
const error = "error" in result ? result.error : "Unknown error";
this.disableCharm(charmId, bg, error);
} else {
this.recordSuccess(charmId, bg);
}
Expand All @@ -242,16 +243,22 @@ export class SpaceManager {
private recordSuccess(charmId: string, bg: Cell<BGCharmEntry>) {
bg.update({
lastRun: Date.now(),
status: "Success",
});
if (this.schedulableBgs.has(charmId)) {
this.addPendingRun(charmId, bg, this.rerunIntervalMs / 1000);
}
}

private disableCharm(charmId: string, bg: Cell<BGCharmEntry>) {
private disableCharm(
charmId: string,
bg: Cell<BGCharmEntry>,
error?: string,
) {
bg.update({
disabledAt: Date.now(),
lastRun: Date.now(),
status: error ? error : "Disabled",
Copy link
Collaborator

@jsantell jsantell Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider: error ?? "Disabled"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deno typescript doesn't like it:

Property 'error' does not exist on type '{ success: boolean; data?: any; charmId: string; } | { success: boolean; error: string; charmId: string; } | { success: boolean; error: string; }'.

  Property 'error' does not exist on type '{ success: boolean; data?: any; charmId: string; }'.

deno-ts(2339)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error is a string | undefined, not sure what that type is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

I think it might just be typescript being overly cautious?

});
this.schedulableBgs.delete(charmId);
this.pendingRuns = this.pendingRuns.filter((r) => r.charmId !== charmId);
Expand Down
7 changes: 4 additions & 3 deletions recipes/bgAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
UI,
} from "@commontools/builder";

// NOTE(ja): this must be the same as the schema in utils/src/updaters.ts
const BGCharmEntrySchema = {
type: "object",
properties: {
Expand All @@ -17,9 +18,9 @@ const BGCharmEntrySchema = {
integration: { type: "string" },
createdAt: { type: "number" },
updatedAt: { type: "number" },
disabledAt: { type: "number" },
lastRun: { type: "number" },
status: { type: "string" },
disabledAt: { type: "number", default: 0 },
lastRun: { type: "number", default: 0 },
status: { type: "string", default: "" },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worthwhile leaving a comment that this schema should (must?) match CharmEntrySchema in @commontools/utils/updaters

Probably a broader question, how does the worker code (via @commontools/utils/updaters) enforce/reject/ignore/handle data on read with a different written schema (e.g. BGCharmEntrySchema here)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call - added the note

w.r.t. the broader question - as long as the schemas are additive the new defaults are loaded.

but I think we have to be careful - as I don't think anything stops us from breaking things with schema changes that aren't backwards compatible

},
required: [
"space",
Expand Down
14 changes: 4 additions & 10 deletions toolshed/routes/integrations/google-oauth/google-oauth.handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
getBaseUrl,
persistTokens,
} from "./google-oauth.utils.ts";
import { addCharmToBG } from "@commontools/utils";
import { addOrUpdateBGCharm } from "@commontools/utils";

import { type CellLink } from "@commontools/runner";

Expand Down Expand Up @@ -195,17 +195,11 @@ export const callback: AppRouteHandler<CallbackRoute> = async (c) => {
"Adding Google integration charm to Gmail integrations",
);

const added = await addCharmToBG({
await addOrUpdateBGCharm({
space,
charmId: integrationCharmId,
integration: "gmail",
integration: "google",
});

if (added) {
logger.info("Added charm to Gmail integrations");
} else {
logger.info("Charm already exists in Gmail integrations");
}
} else {
logger.warn(
{ decodedState },
Expand Down Expand Up @@ -363,7 +357,7 @@ export const backgroundIntegration: AppRouteHandler<
try {
const payload = await c.req.json();

await addCharmToBG({
await addOrUpdateBGCharm({
space: payload.space,
charmId: payload.charmId,
integration: payload.integration,
Expand Down
88 changes: 36 additions & 52 deletions utils/src/updaters.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Cell, getCell, storage } from "@commontools/runner";
import { JSONSchema, Schema } from "@commontools/builder";
import { ID, JSONSchema, Schema } from "@commontools/builder";

// This is the derived space id for toolshed-system
export const SYSTEM_SPACE_ID =
export const BG_SYSTEM_SPACE_ID =
"did:key:z6Mkfuw7h6jDwqVb6wimYGys14JFcyTem4Kqvdj9DjpFhY88";
export const CELL_CAUSE = "bgUpdater-2025-03-18";
export const BG_CELL_CAUSE = "bgUpdater-2025-03-18";

export const CharmEntrySchema = {
type: "object",
Expand All @@ -13,38 +13,32 @@ export const CharmEntrySchema = {
charmId: { type: "string" },
integration: { type: "string" },
createdAt: { type: "number" },
lastRun: { type: "number", default: null },
updatedAt: { type: "number" },
disabledAt: { type: "number", default: null },
runs: { type: "number", default: 0 },
disabledAt: { type: "number", default: 0 },
lastRun: { type: "number", default: 0 },
status: { type: "string", default: "" },
},
required: [
"space",
"charmId",
"integration",
"createdAt",
"updatedAt",
"enabled",
"runs",
"lastRun",
"status",
],
} as const satisfies JSONSchema;
export type BGCharmEntry = Schema<typeof CharmEntrySchema>;

// Define schema for the cell with correct type literals
export const bgUpdaterCharmsSchema = {
type: "object",
properties: {
charms: {
type: "array",
items: CharmEntrySchema,
default: [],
},
},
required: ["charms"],
type: "array",
items: CharmEntrySchema,
default: [],
} as const satisfies JSONSchema;

export type BGUpdaterCharmsSchema = Schema<typeof bgUpdaterCharmsSchema>;

export async function addCharmToBG({
export async function addOrUpdateBGCharm({
space,
charmId,
integration,
Expand All @@ -60,59 +54,49 @@ export async function addCharmToBG({
JSON.stringify(charmsCell.getAsCellLink(), null, 2),
);

// FIXME(ja): if we use IDs might might not need to do this?
// Get current charms data
const charms = charmsCell.get() || [];

// Check if this charm is already in the list to avoid duplicates
const exists = charms.some(
(charm: BGCharmEntry) => charm.space === space && charm.charmId === charmId,
const existingCharmIndex = charms.findIndex(
(charm: Cell<BGCharmEntry>) =>
charm.get().space === space && charm.get().charmId === charmId,
);

if (!exists) {
if (existingCharmIndex === -1) {
console.log("Adding charm to BGUpdater charms cell");
charmsCell.push({
[ID]: `${space}/${charmId}`,
space,
charmId,
integration,
createdAt: Date.now(),
updatedAt: Date.now(),
disabledAt: undefined,
runs: 0,
});
lastRun: 0,
status: "Initializing",
} as unknown as Cell<BGCharmEntry>);

// Ensure changes are synced
await storage.synced();
return true;
}

console.log("Charm already exists in BGUpdater charms cell");
return false;
}
return true;
} else {
console.log("Charm already exists in BGUpdater charms cell, re-enabling");
const existingCharm = charms[existingCharmIndex];
existingCharm.update({
disabledAt: 0,
updatedAt: Date.now(),
status: "Re-initializing",
});

/**
* Get the BGUpdater charms cell
*/
export async function getBGUpdaterCharmsCell() {
if (!storage.hasSigner()) {
throw new Error("Storage has no signer");
}
await storage.synced();

if (!storage.hasRemoteStorage()) {
throw new Error("Storage has no remote storage");
return false;
}
const schema = bgUpdaterCharmsSchema.properties.charms;

const charmsCell = getCell(SYSTEM_SPACE_ID, CELL_CAUSE, schema);

// Ensure the cell is synced
await storage.syncCell(charmsCell, true);
await storage.synced();

return charmsCell;
}

export async function newGetFunc(): Promise<Cell<Cell<BGCharmEntry>[]>> {
export async function getBGUpdaterCharmsCell(): Promise<
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice renames here 👍

Cell<Cell<BGCharmEntry>[]>
> {
if (!storage.hasSigner()) {
throw new Error("Storage has no signer");
}
Expand All @@ -129,7 +113,7 @@ export async function newGetFunc(): Promise<Cell<Cell<BGCharmEntry>[]>> {
default: [],
} as const satisfies JSONSchema;

const charmsCell = getCell(SYSTEM_SPACE_ID, CELL_CAUSE, schema);
const charmsCell = getCell(BG_SYSTEM_SPACE_ID, BG_CELL_CAUSE, schema);

// Ensure the cell is synced
// FIXME(ja): does True do the right thing here? Does this mean: I REALLY REALLY
Expand Down