@jsm-mit/tigi-sharded-package

中文文档
0.12.0发布于 2026/11/01ISCTypeScriptES Module

The one package of the Tigi network group: takes the coordinator's id and hides the coordinator, the core and the workers — for a consumer the group is one canister; plus the rider's few driver-canister endpoints (U-14).

原始使用文档
文档目录5 节
  1. The rider
  2. For the fronts: the chat (U-43)
  3. For the fronts: the driver's own canister (U-45)
  4. Scripts
  5. Publishing

@jsm-mit/tigi-sharded-package

The one package of the Tigi network group: it takes the coordinator's id and hides the coordinator, the core and the workers — for a consumer the group is one canister. It also carries the rider's few driver-canister endpoints (U-14: getRide, cancelRide, getLicence, getRideRoute), built from protocol/tigi-driver.did, so the consumer never sees a driver canister's id.

Status: the rider's side (night 1, U-42); 0.2.0 (stage 2a, U-43) adds the rider's own rating and WHERE the chat with a driver is; 0.3.0 (U-45) adds DriverCanistersActor — the demo lease and the one-time claim code; 0.4.0 (route line, 2026-10-24) adds the driver's route on the map — the line of a tapped offer, the line of the ride — and the exact pin of an offer (pinExact); 0.5.0 (stage 7, ride history, 2026-10-24) adds the places of a past ride (Skąd, Dokąd) and the rating of a past ride from the history. Version 0.1.0 was the first to publish.

The rider

import { ProfilesActor, RidesActor, TestManagerActor, REQUEST_POLL_MS } from "@jsm-mit/tigi-sharded-package";

const profiles = new ProfilesActor(coordinatorId, identity, { host });
await profiles.setMyProfileAsyncUnsafe("Ala");             // C-2: once, before the first request

const rides = new RidesActor(coordinatorId, identity, { host });
const request = await rides.requestRideAsyncUnsafe({ pickup, destination });
localStorage.setItem("tigi-request", request.token);       // rides.resumeRequest(token) after a reload

const snapshot = await request.getAsyncUnsafe();            // every REQUEST_POLL_MS (1 s)
// snapshot.phase: searching | waitingForDriver ("Zamawiam…", a second or two) | accepted | closed
// snapshot.offers: a FIXED list (U-42) — by first arrival, new ones at the end, nothing
//   re-sorted; an offer that left keeps its entry with `gone` (rejected | driverNotFree |
//   taken | closed): draw a grey row of the same height there
// offer.pin: the driver's exact position (offer.pinExact) — the geohash7 centre from an old driver canister
const line = await request.getOfferRouteAsyncUnsafe(offer.offerId);  // ZO-21, after a tap: { toPickup: Geo[]; ride: Geo[] } | undefined
await request.rejectOfferAsyncUnsafe(offer.offerId);        // ✕ → gone "rejected"; the next nearest driver
await request.refreshOfferAsyncUnsafe(offer.offerId);       // a stale (grey) offer asks again
await request.chooseOfferAsyncUnsafe(offer.offerId);        // ordered (= the ride, accepted at once) | busy | offerExpired
                                                            // | driverNotFree ("Kierowca jest już zajęty") | closed
await request.giveUpAsyncUnsafe();
// After "Kierowca anulował kurs" (ride status cancelledByDriver): "Szukaj ponownie" —
const again = await request.searchAgainAsyncUnsafe();       // a new request, the same pickup and destination

const ride = await request.getRideAsyncUnsafe();            // from the driver canister, every RIDE_POLL_MS (2 s)
await request.getRideRouteAsyncUnsafe();                    // K-13: the ride's line while it is active (once; undefined after the end)
await request.cancelRideAsyncUnsafe();                      // before inProgress, once
// ZT-*: a ride at a chosen time (1 h … 7 days ahead; drivers asked by their base, 3 → 25 km by the workers themselves):
const later = await rides.requestScheduledAsyncUnsafe({ pickup, destination, pickupAtMs }); // keep later.token; rides.resumeScheduled(token)
const s = await later.getAsyncUnsafe();                     // phase collecting | booking | booked | started | rescue | closed; offers cheapest first
await later.chooseAsyncUnsafe(s.offers[0].offerId);         // a booking; read again for booked / lastOrderRefused
// started → rides.resumeRequest(s.rideToken) is the ride; rescue → requestRideAsyncUnsafe with the same A, B from T−10 min (RESCUE_BEFORE_MS)
// P-20: card straight to the driver (Stripe) — when ride.cardAccepted, during inProgress:
const card = await request.startCardPaymentAsyncUnsafe(returnUrl); // open → go to card.url; the same page again while it stands
await request.checkCardPaymentAsyncUnsafe();                // back from Stripe (card=paid|cancelled in the URL); ride.cardPayment shows it too
await request.getLicenceAsyncUnsafe();                      // from accepted to the end of the ride
await request.rateDriverAsyncUnsafe(5);                     // after completed, once

const page = await profiles.getMyRidesAsyncUnsafe();        // the history in the core, 20 a page
// 0.5.0 — a line: pickup?, destination? (KU-4; none before stage 7), ratable (KU-5), myRating?
const past = await profiles.getMyRideAsyncUnsafe(page.rides[0].rideRef);  // one line (a reload); undefined until the core has it
if (past?.ratable) await profiles.ratePastRideAsyncUnsafe(past.rideRef, 5);
//   → "ok" | "alreadyRated" (also after rateDriverAsyncUnsafe) | "notRatable" (not completed,
//     the 24 h are over, not in the core yet) | "notAuthorized" (another rider's ride)
const mine = await profiles.getMyRatingAsyncUnsafe();       // PF-4: { average: 4.87, count, sum } | undefined ("Brak ocen")
await rides.getPastRideAsyncUnsafe(page.rides[0].rideRef);  // its details from the driver canister

What the package does for the network (zgloszenie-i-oferty.md):

  • the request goes to every worker whose area reaches 3 km around the pickup (ZO-1), found with the coordinator's prefix map (kept 30 s; a worker's NOT_MY_AREA makes it ask again);
  • 15 s without an offer → 6 km, in the same and in new workers (ZO-8), inside getAsyncUnsafe;
  • one choice at a time over all the workers (ZO-14); after an acceptance, closeRequest otherOfferChosen in the other workers (ZO-15);
  • a stopped canister (an upgrade, IC0508/IC0509) is waited out for about a minute.

Errors: CanisterError (with errorKey; refusalKeyOf(err) gives the worker's refusal: ALREADY_OPEN, NOT_MY_AREA, FULL, CLOSED, ORDERING, DRIVER_NOT_FREE, NOT_COMPLETED, EXPIRED, BLOCKED), CallRefusedAtInspectionStage, CriticalCanisterError.

For the fronts: the chat (U-43)

One chat room per DRIVER in chat-sharded (not per ride): its recipient is the owner of the driver canister, and every Tigi account may open a chat there (the chat asks the Tigi coordinator WHERE the accounts are and the core isUser). The core makes the rooms through the coordinator; this package only says WHERE — the fronts send and read messages with @jsm-mit/chat-sharded-package themselves (this package does not wrap the chat).

import { ChatPlacesActor, driverRoomId } from "@jsm-mit/tigi-sharded-package";
import { ChatsActor, MessagesActor } from "@jsm-mit/chat-sharded-package";

// The rider (PR-25): from `accepted` to the end of the ride.
const place = await request.getChatPlaceAsyncUnsafe();      // { chatCoordinatorId, projectId, roomId } | undefined
if (place) {
    const chats = new ChatsActor(place.chatCoordinatorId, identity, { host });
    const chat = await chats.openChatAsyncUnsafe({ projectId: place.projectId, roomId: place.roomId, customerName: profile.name });
    // … MessagesActor(place.chatCoordinatorId, identity).postAsyncUnsafe / pollNewMessagesAsyncUnsafe
}

// The driver (FK-20): the room of their own canister; they are its recipient.
const driverPlace = await new ChatPlacesActor(coordinatorId, identity, { host }).getDriverChatPlaceAsyncUnsafe(myDriverCanisterId);
// list the chats of driverPlace.roomId (ChatsActor.getMyChatsAsyncUnsafe({ projectId, roomId }))
// and show only the current rider's chat
  • undefined = no chat in this environment yet (no chat config in the core) or a simulated driver (no chat in MVP).
  • The room id is deterministic: driverRoomId(canister) = drv-<canister id> (the core's ChatRoomFunctions.driverRoomId).
  • The rider needs a Tigi profile (setMyProfileAsyncUnsafe) — without one the chat refuses to open it (CanisterError, NotAuthorized).
  • One chat per (room, rider, recipients): the rider has one chat with a driver over all their rides. A demo pool canister taken over by another tester gets that tester as the recipient; the chats with the previous tester stay with them.
  • The room may appear a few seconds after the driver's canister (the core's outbox); openChatAsyncUnsafe answers NotFound until then — try again.

For the fronts: the driver's own canister (U-45)

Internet Identity gives one person a different principal on every origin. The demo lease is taken on the rider's site; the driver signs in on the canister's own front (https://<canister>.icp0.io) with another principal. A fresh lease answers a one-time claim code (15 min, single use; the core keeps only its sha256) that hands the canister to whoever claims it there (fabryka.md F-20).

import { DriverCanistersActor, claimLinkOf, claimCodeOfHash } from "@jsm-mit/tigi-sharded-package";

// The rider's site (the lease holder).
const canisters = new DriverCanistersActor(coordinatorId, identity, { host });
const lease = await canisters.leaseDemoAsyncUnsafe();       // { kind: "leased", canister, claim? } | { kind: "busy", untilMs }
if (lease.kind === "leased" && lease.claim) {
    link = claimLinkOf(lease.canister.canisterId, lease.claim.code);   // "Otwórz swój kanister"
}
const fresh = await canisters.newClaimCodeAsyncUnsafe();    // "Nowy link": the old code stops working
await canisters.extendDemoAsyncUnsafe();                    // F-8: 15 min more
await canisters.getMineAsyncUnsafe();                       // the held demo canister (or the own one) | undefined

// The driver's front, signed in as a non-owner, with #claim=<code> in the address.
const code = claimCodeOfHash(window.location.hash);
if (code) {
    await new DriverCanistersActor(coordinatorId, identity, { host }).claimAsyncUnsafe(canisterId, code);
    // clear the fragment, reload: the driver is the owner now
}
  • A refused claim is always CanisterError NotAuthorized — wrong, expired, used or not this canister's code alike.
  • The lease stays with its holder: getMineAsyncUnsafe, extendDemoAsyncUnsafe and newClaimCodeAsyncUnsafe keep working from the rider's site; ownerPrincipal names the driver after the claim. The chat room of the canister gets the driver as its recipient.
  • The same holder leasing again (no reinstall) gets no code: newClaimCodeAsyncUnsafe.
  • These views carry the driver canister's id: it is the driver's own address.

The Test Manager (stage and the test modes): TestManagerActor.hasSimulatedDriversAsyncUnsafe, spawnDriversAsyncUnsafe (12 drivers), setSpeedUpAsyncUnsafe(request, 10) before choosing. Offers of the simulator have simulated: true.

Scripts

npm run build              # tsc (src → dist) and the type-check of the tests
npm run test-unit          # no replica needed
npm run test-drivers       # (a) 5 real driver canisters, driver-bots, route-stub
npm run test-simulator     # (b) the simulator, 20 requests, 20 rides
npm run test-split         # (c) an area split during traffic
npm run test-claim         # (d) the demo lease and the claim code (U-45)
npm run test-route-line    # (e) the route line of an offer and of a ride
npm run test-ride-history  # (f) the history's places and the rating of a past ride (0.5.0)
npm run test-smoke         # one whole ride with the simulator (run by publish-public)
npm run load-local         # the load test: 200 simulated drivers, 100 riders (docs/load-test-2026-10-24.md)
npm run publish-public     # human-run: publish from master (see below)
npm run sync-declarations  # after a change of a .did (`… protocol` for the protocol and the simulator only)

The end-to-end suites run on the LOCAL replica only (scripts/run-tests-local.sh): a fresh group, local cycles, the driver and simulator wasms of ../../tigi-driver-rust (built there, never changed). Logs and measurements go to /tmp/claude-1000/tigi/ (TEST_LOG_DIR). The load test builds its group with its own coordinator (tigi-sharded-coordinator-load), so the shared local group stays; its settings are environment variables (scripts/load-local.ts, scripts/run-load-local.sh).

Publishing

Human-run only: bump version in package.json by hand (do not commit it), then npm run publish-public on master. The script refuses unless this repo and ../../tigi-driver-rust are both on a clean master equal to origin/master, the version is new on npm, the declarations match the current builds (core, worker, coordinator, protocol, simulator) and the smoke test passes on the LOCAL replica (dfx start first). It stamps tigiSharded.commit and tigiDriverRust.commit into package.json, publishes, then commits "Version bump to " and pushes.

版本与发布记录

@jsm-mit/tigi-sharded-package 共发布 14 个版本,首次发布于 2026/10/23;过去 12 个月发布了 14 个版本。

版本发布日期距今
0.12.0latest2026/11/011 天前
0.11.02026/11/012 天前
0.10.02026/11/012 天前
0.9.02026/11/012 天前
0.8.02026/10/313 天前
0.7.02026/10/313 天前

关于 @jsm-mit/tigi-sharded-package 的常见问题

@jsm-mit/tigi-sharded-package 是什么?

Tigi网络组的一个包:获取协调器的id,并隐藏协调器、核心和工作器——对于消费者来说,组是一个罐子;加上车手的几个驾驶罐端点(U-14)。

如何安装 @jsm-mit/tigi-sharded-package?

在项目目录运行 npm install @jsm-mit/tigi-sharded-package,或使用 pnpm add / yarn add / bun add。

@jsm-mit/tigi-sharded-package 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@jsm-mit/tigi-sharded-package 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@jsm-mit/tigi-sharded-package 可以商用吗?

@jsm-mit/tigi-sharded-package 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@jsm-mit/tigi-sharded-package 还在维护吗?

最新版本 0.12.0 发布于 2026/11/01(1 天前);至今共发布 14 个版本,其中过去 12 个月发布了 14 个;首次发布于 2026/10/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。