@homie-rocks/studio 0.45.3 → 0.46.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +3 -2
- package/docs/rooms-milestone-2-notes.md +345 -0
- package/guides/game/RULES.md +65 -3
- package/guides/game/SKILL.md +46 -2
- package/lib/build-identity.mjs +43 -0
- package/lib/build.mjs +1 -1
- package/lib/cloudflare.mjs +13 -11
- package/lib/crowd-cloud.mjs +88 -0
- package/lib/crowd-diagnostics.mjs +73 -0
- package/lib/crowd-load.mjs +603 -0
- package/lib/crowd-probe/README.md +61 -0
- package/lib/crowd-probe/client.mjs +96 -0
- package/lib/crowd-probe/worker.mjs +72 -0
- package/lib/crowd-probe/wrangler.jsonc +12 -0
- package/lib/crowd-summary.mjs +140 -0
- package/lib/crowd-worker.mjs +496 -0
- package/lib/rules-types.mjs +1 -1
- package/lib/scaffold.mjs +14 -8
- package/lib/template-history.json +84 -0
- package/netplay/NETPLAY.md +3 -1
- package/netplay/netplay.ts +18 -5
- package/package.json +4 -2
- package/rules/core.ts +12 -6
- package/rules/host.ts +132 -17
- package/rules/interest.mjs +320 -51
- package/rules/rules.ts +32 -9
- package/rules/timing.mjs +20 -0
- package/worker/build-id.mjs +2 -0
- package/worker/characters.mjs +41 -0
- package/worker/functions-schema.mjs +16 -0
- package/worker/gate.mjs +436 -53
- package/worker/index.mjs +241 -25
- package/worker/office-page.mjs +11 -0
- package/worker/office.mjs +27 -0
- package/worker/room-store.mjs +113 -11
- package/worker/room.mjs +85 -16
- package/worker/version.mjs +1 -1
- package/worker/world-journal.mjs +109 -0
- package/worker/world-wire.mjs +110 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Characters belong to existing player account IDs. Only trusted room code calls this store.
|
|
2
|
+
* A lease never expires: a delayed room cannot spend a second copy after a timeout.
|
|
3
|
+
* The existing Table namespace supplies per-account objects; no new database or hand-entered ID.
|
|
4
|
+
*/
|
|
5
|
+
export function characterStore(storage) {
|
|
6
|
+
const sql = storage.sql;
|
|
7
|
+
sql.exec('CREATE TABLE IF NOT EXISTS characters (game TEXT PRIMARY KEY, data TEXT NOT NULL)');
|
|
8
|
+
const read = game => JSON.parse(sql.exec('SELECT data FROM characters WHERE game = ?', game).toArray()[0]?.data ?? 'null');
|
|
9
|
+
const write = (game, value) => sql.exec('INSERT INTO characters(game,data) VALUES (?,?) ON CONFLICT(game) DO UPDATE SET data=excluded.data', game, JSON.stringify(value));
|
|
10
|
+
return {
|
|
11
|
+
read,
|
|
12
|
+
acquire(game, holder, schema) {
|
|
13
|
+
return storage.transactionSync(() => {
|
|
14
|
+
const row = read(game) ?? { lease: 0, holder: null, character: null, schema };
|
|
15
|
+
if (row.schema !== schema) return { ok: false, error: 'character-schema-changed' };
|
|
16
|
+
if (row.holder && row.holder !== holder) return { ok: false, error: 'character-held', holder: row.holder };
|
|
17
|
+
if (!row.holder) { row.holder = holder; row.lease += 1; write(game, row); }
|
|
18
|
+
return { ok: true, ...row };
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
return(game, holder, lease, character) {
|
|
22
|
+
return storage.transactionSync(() => {
|
|
23
|
+
const row = read(game);
|
|
24
|
+
if (!row || lease > row.lease) throw new Error('Unknown character lease');
|
|
25
|
+
// Lost acknowledgement or an older room: success means retire that outbox,
|
|
26
|
+
// never apply an old return to a newer lease.
|
|
27
|
+
if (lease < row.lease || !row.holder) return { ok: true };
|
|
28
|
+
if (row.holder !== holder) throw new Error('Wrong character holder');
|
|
29
|
+
write(game, { ...row, holder: null, character });
|
|
30
|
+
return { ok: true };
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function characterRequest(env, account, body) {
|
|
37
|
+
const object = env.TABLE.get(env.TABLE.idFromName(`__player/${account}`));
|
|
38
|
+
const response = await object.fetch('https://player/__character', { method: 'POST', body: JSON.stringify(body) });
|
|
39
|
+
if (!response.ok) throw new Error('Character storage unavailable');
|
|
40
|
+
return response.json();
|
|
41
|
+
}
|
|
@@ -53,3 +53,19 @@ ALTER TABLE customer_offers ADD COLUMN expires_at INTEGER NOT NULL DEFAULT 0;
|
|
|
53
53
|
CREATE INDEX customer_offers_expiry ON customer_offers(expires_at);
|
|
54
54
|
${FUNCTIONS_MIGRATION.slice(FUNCTIONS_MIGRATION.indexOf('CREATE TRIGGER')).replace(/CREATE TRIGGER (\w+)/g, 'DROP TRIGGER IF EXISTS $1; CREATE TRIGGER $1').replace(/ON (shop_orders|purchase_orders|meta|mcp_audit)\n?WHEN /g, 'ON $1 WHEN ').replace(/WHEN OLD.paid_at/g, "WHEN EXISTS (SELECT 1 FROM function_subscriptions WHERE type='order.paid') AND OLD.paid_at").replace(/WHEN OLD.status/g, "WHEN EXISTS (SELECT 1 FROM function_subscriptions WHERE type='order.refunded') AND OLD.status").replace(/ON app_records BEGIN/g, "ON app_records WHEN EXISTS (SELECT 1 FROM function_subscriptions WHERE type='record.changed') BEGIN").replace(/WHEN NEW.key/g, "WHEN EXISTS (SELECT 1 FROM function_subscriptions WHERE type='payment.refunded') AND NEW.key").replace(/WHEN NEW.outcome/g, "WHEN EXISTS (SELECT 1 FROM function_subscriptions WHERE type='tool.called') AND NEW.outcome").replace(/INTO studio_events(?= VALUES|\n SELECT)/g,'INTO studio_events(id,type,data,at)')}
|
|
55
55
|
`;
|
|
56
|
+
|
|
57
|
+
// SQLite may evaluate AND operands in either order. Guard parsing itself: meta also
|
|
58
|
+
// holds plain strings, including the key used to sign player room tickets.
|
|
59
|
+
export const FUNCTIONS_META_MIGRATION_FILE = '0020_function_meta.sql';
|
|
60
|
+
export const FUNCTIONS_META_MIGRATION = ['INSERT', 'UPDATE'].map(op => `
|
|
61
|
+
DROP TRIGGER IF EXISTS money_function_refund_${op.toLowerCase()};
|
|
62
|
+
CREATE TRIGGER money_function_refund_${op.toLowerCase()} AFTER ${op} ON meta
|
|
63
|
+
WHEN CASE WHEN NEW.key LIKE 'shop-money:%:refund:%' AND json_valid(NEW.value)
|
|
64
|
+
THEN EXISTS (SELECT 1 FROM function_subscriptions WHERE type='payment.refunded')
|
|
65
|
+
AND json_extract(NEW.value,'$.status')='succeeded'
|
|
66
|
+
AND NOT EXISTS (SELECT 1 FROM studio_events WHERE id='refund:' || json_extract(NEW.value,'$.id'))
|
|
67
|
+
ELSE 0 END BEGIN
|
|
68
|
+
INSERT OR IGNORE INTO studio_events(id,type,data,at)
|
|
69
|
+
SELECT 'refund:' || json_extract(NEW.value,'$.id'),'payment.refunded',
|
|
70
|
+
json_object('refund',json_extract(NEW.value,'$.id'),'payment',json_extract(NEW.value,'$.payment_intent'),'amount',json_extract(NEW.value,'$.amount'),'currency',json_extract(NEW.value,'$.currency')),json_extract(NEW.value,'$.at')*1000;
|
|
71
|
+
END;`).join('\n');
|