Back to Limb Race
SOURCE / PINNED RELEASE

Made of little things.

Limb Race

Release
6a8ae4fc626e…
Author-recorded commit
008c01ee6454…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-c2869f12ad8

Archive hash verified: 7be8492d1f89b891…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/levels.ts
import {identity, outbox, storage} from '@napplet/sdk';
import {appDataCollection, type AppDataHost, type AppRecord} from '../docs/examples/app-data.js';
import {runtimeHasDomain} from './domain-availability.js';
import project from '../napplet.json';
import {starterLevel, validateLevel, type LevelData, type LevelSnapshot} from './level-model.js';

export type LevelRecord = AppRecord<LevelData>;
export type LevelDraft = {id:string; title:string; version:number; data:LevelData; base:LevelRecord|null};
const KEY='limb-race-levels-v1';
const freshId=()=>globalThis.crypto?.randomUUID?.()??`level-${Date.now()}-${Math.random().toString(36).slice(2)}`;
export function createDraft(data=starterLevel(),title='My new track'):LevelDraft {return {id:freshId(),title,version:1,data:validateLevel(data),base:null};}
export function draftSnapshot(draft:LevelDraft):LevelSnapshot {return {key:`draft:${draft.id}:${draft.version}`,title:draft.title.trim()||'Untitled track',data:validateLevel(draft.data)};}
export function recordSnapshot(record:LevelRecord):LevelSnapshot {
  if(record.deleted||!record.data)throw new Error('This level was unpublished.');
  return {key:`custom:${record.revision}`,title:record.title,data:validateLevel(record.data)};
}
export async function viewerKey():Promise<string>{try{return runtimeHasDomain('identity')?await identity.getPublicKey():'';}catch{return '';}}
export class LevelStore {
  drafts:LevelDraft[]=[];
  private saves:Promise<unknown>=Promise.resolve();
  async load():Promise<void>{
    if(!runtimeHasDomain('storage'))return;
    const raw=await storage.getItem(KEY);if(!raw)return;
    const values=JSON.parse(raw);if(!Array.isArray(values))throw new Error('Saved levels could not be read.');
    this.drafts=values.slice(0,20).flatMap((value:LevelDraft)=>{
      try {
        if(!value||typeof value.id!=='string'||!/^[a-zA-Z0-9_-]{1,64}$/.test(value.id)||typeof value.title!=='string'||value.title.length>60||!Number.isSafeInteger(value.version)||value.version<1)return [];
        const data=validateLevel(value.data);
        const base=value.base&&typeof value.base.author==='string'&&/^[a-f0-9]{64}$/.test(value.base.revision)?value.base:null;
        return [{id:value.id,title:value.title,version:value.version,data,base}];
      }catch{return [];}
    });
  }
  async save(draft:LevelDraft):Promise<boolean>{
    const existing=this.drafts.findIndex(d=>d.id===draft.id);
    if(existing<0&&this.drafts.length>=20)throw new Error('You have 20 drafts. Remove one before creating another.');
    const copy=structuredClone(draft);
    if(existing<0)this.drafts.unshift(copy);else this.drafts[existing]=copy;
    return this.persist();
  }
  async remove(id:string):Promise<boolean>{this.drafts=this.drafts.filter(d=>d.id!==id);return this.persist();}
  private async persist():Promise<boolean>{
    if(!runtimeHasDomain('storage'))return false;
    const data=JSON.stringify(this.drafts);
    const next=this.saves.catch(()=>{}).then(()=>storage.setItem(KEY,data));this.saves=next;await next;return true;
  }
}
let collectionPromise:ReturnType<typeof appDataCollection<LevelData>>|undefined;
export function levelCollection(){
  if(!collectionPromise){
    // The supplied helper observes soyLI's documented policy hint. Operational
    // identity/outbox calls still use SDK wrappers; no app-owned handshake.
    const injected=(globalThis as unknown as {napplet?:AppDataHost}).napplet;
    // Keep the public library on the creation's declared storage relay. soyLI's
    // preview prepends its ephemeral CVM transport, which cannot store NIP-78.
    // This is only a request: the host still requires a configured destination.
    collectionPromise=appDataCollection({collection:'levels',schema:'limb-race.level',version:1,validate:validateLevel,relays:project.relays.length?project.relays.slice(0,1):undefined,host:{...injected,identity,outbox} as AppDataHost}).catch(error=>{collectionPromise=undefined;throw error;});
  }
  return collectionPromise;
}
export async function latestLevel(record:LevelRecord):Promise<LevelRecord>{
  const library=await levelCollection(),result=await library.get(record.author,record.id,record.scope);
  if(result.incomplete)throw new Error('Some relays did not respond. Retry before editing this level.');
  if(!result.record)throw new Error('This level is unavailable. Your draft is kept.');
  return result.record;
}
export class LevelPublisher {
  private pending:{fingerprint:string;change:Awaited<ReturnType<Awaited<ReturnType<typeof levelCollection>>['prepare']>>}|null=null;
  clear():void{this.pending=null;}
  async publish(draft:LevelDraft,deleted=false):Promise<LevelRecord>{
    const fingerprint=JSON.stringify([draft.id,draft.title,draft.data,draft.base?.revision,deleted]);
    if(this.pending?.fingerprint!==fingerprint){
      const library=await levelCollection();
      if(draft.base){
        const latest=await latestLevel(draft.base);
        if(latest.revision!==draft.base.revision)throw new Error('This level changed elsewhere. Open the latest version in Community, or make a copy of your draft.');
      }
      const change=await library.prepare({id:draft.id,title:draft.title.trim(),data:draft.data,base:draft.base,deleted});
      this.pending={fingerprint,change};
    }
    const record=await this.pending.change.publish();this.pending=null;return record;
  }
}
export function levelError(error:unknown):string{
  const message=error instanceof Error?error.message:'Could not reach the level library. Retry.';
  if(/not-signed-in|identity-required/.test(message))return 'Connect your Nostr identity in the host to publish. Your draft is kept.';
  if(/app-data-unavailable/.test(message))return 'This host needs the updated public-level sharing support. Your draft is kept locally; try an updated host.';
  if(/relay-not-configured/.test(message))return `Add ${project.relays[0]||'a level storage relay'} in the host Network settings, then retry. Your draft is kept.`;
  if(/identity-changed|owner-mismatch/.test(message))return 'The selected identity changed. Use the original identity, or make a copy to publish as yourself.';
  if(/stale|app-data-conflict/.test(message))return 'This level changed elsewhere. Open the latest version in Community, or make a copy of your draft.';
  if(/denied|reject|cancel/.test(message))return 'Publishing was not approved. Your draft is kept; you can retry.';
  return message.slice(0,240);
}