Documentation
¶
Overview ¶
Package spaceimpl is the concrete implementation of space.Service and space.Space. The split is the same as the legacy SDK: the public space package owns interfaces and types, this internal package owns behavior — sdk.Open constructs a *spaceimpl.Service and returns it as space.Service.
v1 scope: Create / Get / List / Delete on regular spaces, mediated by techspace for the index. Other Space sub-APIs (ACL, Members, Types, Properties, SyncStatus, Modify, Delete, Subscribe) are not wired yet and return zero values.
Index ¶
- Constants
- Variables
- func MaterializeBlock(rec techspace.SpaceIndexRecord) error
- func NewIdentitiesAPI(tsp *techspace.Service) space.IdentitiesAPI
- func NewPubSubAPI(app *anysyncx.App, spaceId string) space.PubSubAPI
- func SetDeletionReconcileIntervalForTest(d time.Duration) (restore func())
- func SetJoinReconcileIntervalForTest(d time.Duration) (restore func())
- func SetOneToOneInboxIntervalsForTest(poll, retry time.Duration) (restore func())
- type PayloadsAPI
- func (p *PayloadsAPI) DeleteRows(ctx context.Context, ownerId string, fileIds []string) error
- func (p *PayloadsAPI) FindRow(ctx context.Context, fileId string) (payloads.Row, error)
- func (p *PayloadsAPI) GetRow(ctx context.Context, ownerId, fileId string) (payloads.Row, error)
- func (p *PayloadsAPI) ListRows(ctx context.Context, ownerId string) ([]payloads.Row, error)
- func (p *PayloadsAPI) ObjectId(ctx context.Context, ownerId string) (string, error)
- func (p *PayloadsAPI) RegisterFile(ctx context.Context, ownerId string, opts RegisterFileOpts) (fileId, payloadsObjId string, err error)
- func (p *PayloadsAPI) RegisterFiles(ctx context.Context, ownerId string, files []RegisterFileOpts) (fileIds []string, payloadsObjId string, err error)
- func (p *PayloadsAPI) SetNetworkSign(ctx context.Context, ownerId, fileId, networkSign string) error
- func (p *PayloadsAPI) SetNetworkSigns(ctx context.Context, ownerId string, signs map[string]string) error
- type RegisterFileOpts
- type Service
- func (s *Service) AcceptInvite(ctx context.Context, spaceId string) (space.Space, error)
- func (s *Service) AcceptOneToOne(ctx context.Context, spaceId string) (space.Space, error)
- func (s *Service) CancelJoin(ctx context.Context, spaceId string) error
- func (s *Service) ClaimActive(ctx context.Context, app string) error
- func (s *Service) Close(_ context.Context) error
- func (s *Service) Create(ctx context.Context, req space.CreateRequest) (space.Space, error)
- func (s *Service) Datasets() []space.DatasetSchema
- func (s *Service) DeclineInvite(ctx context.Context, spaceId string) error
- func (s *Service) DeclineOneToOne(ctx context.Context, spaceId string) error
- func (s *Service) Delete(ctx context.Context, spaceId string) error
- func (s *Service) DeleteDevice(ctx context.Context, peerId string) error
- func (s *Service) DeleteTree(ctx context.Context, spaceId, treeId string) error
- func (s *Service) Derive(ctx context.Context, req space.DeriveRequest) (space.Space, error)
- func (s *Service) DeriveId(ctx context.Context, req space.DeriveRequest) (string, error)
- func (s *Service) Evict(ctx context.Context, spaceId string) error
- func (s *Service) Get(ctx context.Context, spaceId string) (space.Space, error)
- func (s *Service) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error)
- func (s *Service) HasTree(ctx context.Context, spaceId, treeId string) (bool, error)
- func (s *Service) Join(ctx context.Context, req space.JoinRequest) (space.Space, error)
- func (s *Service) JoinGuest(ctx context.Context, invite string) (space.Space, error)
- func (s *Service) KickProfiles()
- func (s *Service) List(ctx context.Context) ([]space.SpaceInfo, error)
- func (s *Service) ListDevices(ctx context.Context) ([]space.Device, error)
- func (s *Service) MarkTreeDeleted(ctx context.Context, spaceId, treeId string) error
- func (s *Service) OffloadSpace(ctx context.Context, spaceId string)
- func (s *Service) OnFileJobChange(job status.Job, _ bool)
- func (s *Service) OneToOne(ctx context.Context, otherIdentity string) (space.Space, error)
- func (s *Service) PushKeys(ctx context.Context, spaceId string) (spaceKey crypto.PrivKey, encKey crypto.SymKey, err error)
- func (s *Service) PutTree(ctx context.Context, spaceId string, ...) error
- func (s *Service) Query(objectId, dataset string) space.Query
- func (s *Service) RegisterIncoming(ctx context.Context, peerIdentity string, displayHint space.AccountMetadata) error
- func (s *Service) ResolveIdentityProfiles(ctx context.Context)
- func (s *Service) ResolveIdentityProfilesAsync()
- func (s *Service) ResolveIntent(ctx context.Context, spaceId, ownerId string, root cid.Cid) (fileId string, ok bool, err error)
- func (s *Service) ResumePendingJoins()
- func (s *Service) RunFileJob(ctx context.Context, job status.Job) error
- func (s *Service) SetDevice(ctx context.Context, up space.DeviceUpsert) error
- func (s *Service) SetFiles(up *upload.Service, fe *fetch.Service, st *filestore.Store, q *status.Queue)
- func (s *Service) SetOnSpaceBorn(fn func(spaceId string))
- func (s *Service) SetReadSync(rs *readsync.Service)
- func (s *Service) SetSettings(ctx context.Context, spaceId string, set map[string]any, unset []string) error
- func (s *Service) ShouldPullTree(ctx context.Context, spaceId, treeId string, ...) bool
- func (s *Service) SpaceFiles(ctx context.Context, spaceId string) (map[string]bool, error)
- func (s *Service) SpaceIndexObjectId() string
- func (s *Service) StartOneToOneInbox(ctx context.Context)
- func (s *Service) Status(spaceId string) space.SpaceSyncStatus
- func (s *Service) StoreFor(spaceId string) *spaceobjects.Store
- func (s *Service) Subscribe(cb func(space.SpaceListEvent)) (cancel func())
- func (s *Service) SubscribeStatus(cb func(space.SpaceSyncStatus)) (cancel func())
- func (s *Service) SweepOrphanCollections(ctx context.Context)
- func (s *Service) SyncSpaceList(ctx context.Context) error
- func (s *Service) Track(ctx context.Context, spaceId string) error
- func (s *Service) WaitListSynced(ctx context.Context) error
Constants ¶
const ( MemberFieldId = "id" // == Member.Identity MemberFieldPermission = "permission" MemberFieldStatus = "status" MemberFieldName = "name" MemberFieldDescription = "description" MemberFieldIcon = "icon" MemberFieldRequestId = "requestId" // pending join records only )
Member-row field names. All strings except where noted.
const MembersCollection = "members"
MembersCollection is the per-space any-store collection holding the materialised members view. One row per identity (active members, removed-member tombstones, and pending join requests). Kept in sync by the per-space memberWatcher; not user-writeable.
Naming follows the existing per-space convention from spaceobjects.SpaceObjectsCollection: <spaceId>/<collection>.
Variables ¶
var ErrInviteAcceptPending = fmt.Errorf("spaceimpl: %w", space.ErrInviteAcceptPending)
ErrInviteAcceptPending is returned by AcceptInvite when the accept was recorded but the space content is not pullable yet. Wraps the public space.ErrInviteAcceptPending for errors.Is.
var ErrJoinPending = fmt.Errorf("spaceimpl: %w", space.ErrJoinPending)
ErrJoinPending is returned by Join after a RequestToJoin invite was successfully posted but the owner has not yet accepted. Wraps the public space.ErrJoinPending for errors.Is.
Functions ¶
func MaterializeBlock ¶
func MaterializeBlock(rec techspace.SpaceIndexRecord) error
MaterializeBlock reports why rec must not be materialized — loaded, and, when local storage is absent, pulled from the responsible nodes (any-sync's NewSpace falls back to a SpacePull bootstrap on missing storage) — or nil when loading is allowed. "Nothing is downloaded until accepted" is the whole materialization gate: it guards every pending flavor, not just direct-add invites. Every accept path flips its row (or calls the unguarded load) before materializing, so legitimate loads pass:
- join: the ACL waiter's loadJoinedSpace uses load directly (the row stays "joining" until the load succeeds);
- incoming 1-1: activateOneToOne flips both statuses to active first;
- direct-add invite: AcceptInvite flips the row to active before it loads.
Shared with sdk.Open's boot eager-load, which must skip pending rows even when storage exists (a pre-guard build may have materialized one; eager-loading it would run headsync/treesyncer on a space this account can't read yet).
The predicate is the mapStatus classification, not the raw fields, so the guard and the surfaced Status can never disagree. In particular acceptance is account-scoped: a 1-1 accepted or initiated on ANY of the account's devices carries synced remote=active, which mapStatus resolves as Active over a stale device-local pending — such a row must load, not demand a per-device re-accept. Conversely a bare 1-1 row synced in before this device set any status classifies as pending and blocks.
func NewIdentitiesAPI ¶
func NewIdentitiesAPI(tsp *techspace.Service) space.IdentitiesAPI
NewIdentitiesAPI builds the account-global identities directory surface over the tech space.
func NewPubSubAPI ¶
NewPubSubAPI returns the PubSubAPI for spaceId. Also used by the SDK root for the account-wide surface (bound to the tech space).
func SetDeletionReconcileIntervalForTest ¶
SetDeletionReconcileIntervalForTest overrides the reconcile poll interval and returns a func that restores the previous value. Test seam only — lets e2e exercise the inbound-detection tick without waiting the full production interval. Call before opening the SDK (the interval is read once when the loop starts).
func SetJoinReconcileIntervalForTest ¶
SetJoinReconcileIntervalForTest overrides the reconcile poll interval and returns a func that restores the previous value. Test seam only — the interval is read once when the loop starts, so call before opening the SDK.
func SetOneToOneInboxIntervalsForTest ¶
SetOneToOneInboxIntervalsForTest overrides the notifier poll and invite-retry intervals, returning a restore func. Test seam only — call before SDK.Open (the loops read the values once at start).
Types ¶
type PayloadsAPI ¶
type PayloadsAPI struct {
// contains filtered or unexported fields
}
PayloadsAPI is the SDK-internal surface over the per-owner file `payloads` objects (SYN-21). Consumed by the upload path (SYN-27); a public Files API arrives with SYN-30.
The per-owner payloads object is created LAZILY: RegisterFile derives it on first use; read paths resolve the deterministic derived id and treat a missing tree as "no rows" — they never create it. Owners with no files never grow a child tree.
func (*PayloadsAPI) DeleteRows ¶
DeleteRows removes rows from the owner's payloads object as ONE change (the delete write shape — payloads.Handler tombstones each record; the deletion syncs like any other change). The rows must exist as far as the caller knows — a delete of an already-deleted row is absorbed by the CRDT, not an error here.
func (*PayloadsAPI) FindRow ¶
FindRow resolves a bare fileId to its row: the local index first (one KV read + one row read), falling back to a scan of the space's payloads objects for rows that arrived via sync, and backfilling the index on the hit.
func (*PayloadsAPI) GetRow ¶
GetRow returns one typed row, unsealed when the caller holds the space key (Sealed stays true otherwise). space.ErrNotFound when the owner has no payloads object, no such row, or a tombstoned row.
func (*PayloadsAPI) ListRows ¶
ListRows returns every live row of the owner's payloads object, unsealed when possible. Empty (nil) when the object doesn't exist.
func (*PayloadsAPI) ObjectId ¶
ObjectId resolves the deterministic payloads-object id for an owner without creating anything.
The id depends on the owner's CLASS (signed → parented shape, derived → unparented; see payloadsDeriveOpts), so it is resolvable only when something local discloses that class:
- the owner's head entry — full tree, selective-sync stub, or a deleted owner (the entry survives deletion) — carries IsDerived;
- failing that, an existing payloads tree self-identifies its shape: either shape can land before its owner (any-sync stores a child in any arrival order), so both candidate ids are probed.
When neither is present the class is genuinely unknowable locally — a signed owner that hasn't synced and a derived owner with no payloads tree look identical — and ObjectId returns payloads.ErrOwnerUnknown rather than guessing an id that would flip once the owner arrives. Callers retry after sync delivers the owner (or its payloads tree).
The WRITE path (RegisterFiles) resolves the class the same way and refuses with the same sentinel; it never guesses a shape to create.
func (*PayloadsAPI) RegisterFile ¶
func (p *PayloadsAPI) RegisterFile(ctx context.Context, ownerId string, opts RegisterFileOpts) (fileId, payloadsObjId string, err error)
RegisterFile is the single-file convenience over RegisterFiles.
func (*PayloadsAPI) RegisterFiles ¶
func (p *PayloadsAPI) RegisterFiles(ctx context.Context, ownerId string, files []RegisterFileOpts) (fileIds []string, payloadsObjId string, err error)
RegisterFiles seals each file's Enc under the current space key and writes all rows into the owner's payloads object as ONE change (one DAG entry, one sync hop — bulk flows like a pasted doc with hundreds of images register in minimum hops), lazily deriving the object on first use. Returns the fileIds in input order (derived from the creating change: seed, seed:1, …) and the payloads objectId.
func (*PayloadsAPI) SetNetworkSign ¶
func (p *PayloadsAPI) SetNetworkSign(ctx context.Context, ownerId, fileId, networkSign string) error
SetNetworkSign is the single-file convenience over SetNetworkSigns.
func (*PayloadsAPI) SetNetworkSigns ¶
func (p *PayloadsAPI) SetNetworkSigns(ctx context.Context, ownerId string, signs map[string]string) error
SetNetworkSigns records the node's durable receipts on existing rows as ONE change (fileId → networkSign) — the sign phase of a bulk upload lands in one hop. The payloads object must already exist; a sign for a file that was never registered is a caller bug.
type RegisterFileOpts ¶
type RegisterFileOpts struct {
// RootCid is the UnixFS root of the encrypted file. Empty selects
// the inline tier (bytes in Enc.Inline, no S3, no sign).
RootCid string
// Size is the plaintext byte size.
Size int64
// NetworkSign optionally carries an existing durable receipt at
// create time (a BIND reusing already-durable content).
NetworkSign string
// Enc is the member-only plaintext this call seals.
Enc payloads.EncPayload
}
RegisterFileOpts describes one file registration.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service implements space.Service backed by the any-sync app and the tech-space's space-index. Held by the SDK for its lifetime.
Per-space state — the spaceobjects.Store with its CRDT controllers and shared VersionAllocator — is created lazily on first access and held for the life of the SDK. The any-sync side of each space goes through the App's space cache and may TTL-evict; our store stays loaded so live writes don't pay the controller-build cost twice.
func New ¶
func New(app *anysyncx.App, tsp *techspace.Service, indexer space.Indexer, db anystore.DB, extTypes []handler.Type, extCollections []handler.Collection, modules []handler.Module) *Service
New returns a Service ready to be returned via SDK.Spaces(). The db argument is the shared SDK DB where CRDT collections live. extTypes must already have been validated by spaceobjects.ValidateExternalTypes at the SDK boundary. indexer is the seam through which the tech-space mirrors converged in-space spaceIndex state into its rows; usually tsp itself (which satisfies space.Indexer).
func (*Service) AcceptInvite ¶
AcceptInvite approves a direct-add invite by space id: flips the SYNCED row status to active (every device converges — declined is non-terminal, so this also un-declines) and loads the space. The account is already an ACL member, so unlike Join there is nothing to wait for on the ACL — just a pull-until-available load. One bounded synchronous attempt is made; if the content isn't pullable yet (add still propagating, offline) it returns (nil, ErrInviteAcceptPending) and the join controller finishes the load durably in the background.
func (*Service) AcceptOneToOne ¶
AcceptOneToOne approves an incoming pending 1-1 by space id: materializes its storage and activates it. The peer identity is read off the row, so the caller only needs the id surfaced in the space list. Also un-declines a previously declined 1-1 (an explicit accept overrides the sticky marker).
func (*Service) CancelJoin ¶
CancelJoin withdraws this account's pending join request — see space.Service.CancelJoin. Rides the joining client, never the space: a pending join holds no local storage and must not materialize one (the guard Get enforces), while the request record lives on the ACL chain the nodes serve directly.
func (*Service) ClaimActive ¶
ClaimActive claims the active role for app on this device (see space.Service.ClaimActive for the contract).
func (*Service) Close ¶
Close stops what the SDK owns per space: the background goroutines, the inbox, the deletion and join controllers, every watcher, then each loaded space's Store. The any-sync side of each space is owned by the App's space cache and torn down separately by App.Close.
func (*Service) Create ¶
Create creates a new regular space owned by the authenticated account, and writes its space-index entry into the tech space.
func (*Service) Datasets ¶
func (s *Service) Datasets() []space.DatasetSchema
Datasets returns the JSON-Schema description of the tech-space system datasets (spaces, profile, devices, …) for discovery. The tech handle's Space.Datasets lists everything the tech Store hosts, bundle datasets included.
func (*Service) DeclineInvite ¶
DeclineInvite rejects a direct-add invite. Writes the synced, sticky InviteDeclined marker so the request is suppressed on every device; a later AcceptInvite overrides it (non-terminal). No ACL write happens — the account stays a member on the space's ACL — and nothing was materialized, so there is nothing to offload.
func (*Service) DeclineOneToOne ¶
DeclineOneToOne rejects an incoming 1-1. Writes the synced, sticky oneToOneDeclined marker so the request is suppressed on every device; an explicit OneToOne(peer) later overrides it. Pending rows carry no storage, so there is nothing to offload.
func (*Service) Delete ¶
Delete removes a space. It is offline-first and returns as soon as the local work is done — no network round trip on the call path:
- write the SYNCED remoteStatus=deleted tombstone (propagates the delete to the account's other devices, and is the durable intent the reconciler scans; also drives the Subscribe `Removed` event);
- offload all local state immediately (reclaim disk even offline);
- kick the deletion reconciler, which sends the signed SpaceDelete to the coordinator now (if online) or on a later tick (when it reconnects). Only owners' deletes reach the coordinator; for a non-owned space this offloads locally and the reconciler no-ops.
The tech-space row is never physically removed — it stays in List with Status = StatusDeleted as a sticky tombstone.
1-1 spaces take a separate path: they are derived (re-creatable) and not owned on the network, so deleting one must NOT remove it from the nodes. Instead the SYNCED, non-terminal oneToOneDeleted marker propagates the delete to the account's other devices — each offloads its local copy — while the row stays re-creatable (a later OneToOne(peer) flips it back to active). No coordinator SpaceDelete is ever sent.
The tech space (system-owned, never in the list) and seed-derived spaces (rows flagged FieldDerived — see space.ErrIsDerivedSpace) are refused outright.
func (*Service) DeleteDevice ¶
DeleteDevice prunes a device row (see space.Service.DeleteDevice for the contract; the techspace method owns the exists-check that keeps a typo from minting a permanent tombstone).
func (*Service) DeleteTree ¶
DeleteTree performs the per-tree cleanup the deletion-manager drives: marks the any-sync tree storage deleted via tree.Delete() and drops our cached *object.Object. Tech-space defers to its own adapter (the index tree is never explicitly deleted).
func (*Service) Derive ¶
Derive creates (or rehydrates) a deterministic space from req.Seed. The same (account, seed) pair always produces the same spaceId, so repeat calls are idempotent and just return the existing space if it already exists locally.
func (*Service) DeriveId ¶
DeriveId returns the deterministic spaceId for req without creating or loading the space — pure computation over the account keys and seed. Same id as Derive(req).Id() for the same request.
func (*Service) Evict ¶
Evict closes a space without deleting anything: watchers stop, the Store and the any-sync commonspace handle are released. Disk state — the any-sync per-space DB and every SDK CRDT collection — stays, so a later Get reopens the space from local storage (and any-sync resumes syncing it). Idempotent: evicting a space that isn't open is a no-op.
The tech space is refused — it is the registry Get itself depends on.
func (*Service) Get ¶
Get returns a handle to a known space. Eagerly loads the any-sync side via the cache so periodic headsync / syncacl start running from this point — without this, Get only consults the tech-space index and the per-space components stay dormant until the first Modify or per-object Query (the cold paths that go through store.Get(objectId) → app.GetSpace). QueryObjects and members reads satisfied from local storage would otherwise leave a peer unable to receive pushed changes for an arbitrarily long stretch.
Mirrors the eager-load that Create / Derive / OneToOne already do.
The tech space id returns the restricted tech handle (no registry row, no load, no index wiring) — see space.ErrUnsupported.
func (*Service) GetTree ¶
func (s *Service) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error)
GetTree resolves a tree by (spaceId, treeId). Tech-space lookups route to techspace.Service; everything else loads via the per-space Store, which builds the listener-bound *object.Object so inbound changes flow into the controller.
func (*Service) Join ¶
Join sends a join request via the invite. v1 only supports RequestToJoin invites — AnyoneCanJoin is deferred until any-sync ships v2 of that invite type.
Behavior: decode invite, send RequestJoin RPC to the network, write a tech-space record with LocalStatus=Joining. Returns (nil, ErrJoinPending) on success — the joiner doesn't yet have local space storage; that lands after the owner accepts and the space syncs down. Callers poll Service.List for the status flip to StatusActive.
func (*Service) JoinGuest ¶
JoinGuest adds a space via a guest invite — see space.Service.JoinGuest. No ACL write and no approval: the invite key IS the shared read-only guest identity, already an active ACL member. The row is recorded durably (synced guest key + device-local loading marker) before the single bounded load attempt, so a crash or offline start resumes in the join controller.
func (*Service) KickProfiles ¶
func (s *Service) KickProfiles()
KickProfiles queues an identityRepo refetch on every running members watcher's profile loop. Called by the SDK after the caller publishes their own profile via Account.UpdateMetadata so the self-view propagates without waiting for the slow tick.
func (*Service) ListDevices ¶
ListDevices returns the devices-registry snapshot. Unavailability (techspace not open, index object not loadable) surfaces as an error — it must never read as an empty registry.
func (*Service) MarkTreeDeleted ¶
MarkTreeDeleted is the soft-delete hook fired when the settings tree announces a deletion for a tree not present in local storage. Reflects the deletion in local materialized state and drops the cached object so a future load reflects the deleted state. Tech-space defers to its own adapter (the index tree itself is never marked deleted).
func (*Service) OffloadSpace ¶
OffloadSpace tears down all local state for spaceId — in-memory watchers and the Store, the cached any-sync space, every SDK CRDT collection, the any-sync on-disk DB, and the account-values carrier. It does NOT touch the network or the tech-space index row: the row is the durable delete tombstone the reconciler scans, and callers (Delete / inbound detection) set its status before offloading.
Idempotent and retryable: each step tolerates already-gone state so a re-run after a crash mid-offload, or an offload of a space that was never fully loaded, completes without error. Steps are best-effort ONLY for the not-found/already-gone class: a failed collection sweep (meta purge / drop / chunk commit / enumeration error) skips the any-sync storage removal — with the file in place `SpaceExists` stays true, so the deleted-row boot gate and the next Delete re-offload the remainder (committed chunks are durable). The later per-space cleanups (file store, file jobs, account values, identities) are independent of the sweep and always run — notably the file-job removal, since an orphaned job would retry against the deleted space forever.
func (*Service) OnFileJobChange ¶
OnFileJobChange is the queue's OnChange hook: re-derive the file's status and push it to this space's subscribers. Best-effort — a derivation failure drops the event; Status reads stay the truth.
func (*Service) OneToOne ¶
OneToOne reaches out to — or explicitly accepts / un-declines — the 1-1 space shared with otherIdentity. Derives the shared space, materializes its storage, and activates it locally (implicit self-approval). Same id regardless of which side called first; idempotent. Overrides a prior local decline.
func (*Service) PushKeys ¶
func (s *Service) PushKeys(ctx context.Context, spaceId string) (spaceKey crypto.PrivKey, encKey crypto.SymKey, err error)
PushKeys implements pushclient.KeysProvider: loads the space (via the app cache), reads FirstMetadataKey + CurrentReadKey off its ACL state under RLock, and returns the derived push signing key and payload encryption key. Requires read access — a keyless reader/tracker gets an error, matching the server's members-only trust model.
func (*Service) PutTree ¶
func (s *Service) PutTree(ctx context.Context, spaceId string, payload treestorage.TreeStorageCreatePayload) error
PutTree binds a remote-delivered tree payload. Tech-space's index tree is locally created, never put from the network — the tech-space's adapter rejects this, which is the correct behavior.
func (*Service) Query ¶
Query builds a generic read query over a tech-space system object's dataset — same chainable Filter/Sort/Limit/Subscribe surface regular spaces use, but bound to the tech Store. Use SpaceIndexObjectId() for the spaces / profile datasets.
func (*Service) RegisterIncoming ¶
func (s *Service) RegisterIncoming(ctx context.Context, peerIdentity string, displayHint space.AccountMetadata) error
RegisterIncoming records an incoming 1-1 request learned out-of-band (no coordinator) as a device-local pending row for the user to approve. No storage is materialized until AcceptOneToOne. displayHint is an optional name/icon snapshot for the UI. No-op if a row for the derived space already exists — respecting an active space or a sticky decline.
func (*Service) ResolveIdentityProfiles ¶
ResolveIdentityProfiles batch-resolves every directory identity that has a synced symkey but no locally-cached profile yet — the cold-sync case: a fresh device receives many symkeys over tech-space sync but holds no profiles (those are device-local). One IdentityRepoGet covers the whole set instead of one call per identity. Best-effort; run in a goroutine on boot.
func (*Service) ResolveIdentityProfilesAsync ¶
func (s *Service) ResolveIdentityProfilesAsync()
ResolveIdentityProfilesAsync runs ResolveIdentityProfiles in the background, tracked by seedWG and bound to seedCtx so Close cancels and drains it — the goroutine can never race teardown of the tech space or the coordinator client. No-op once closing. Called from sdk.Open on every boot.
func (*Service) ResolveIntent ¶
func (s *Service) ResolveIntent(ctx context.Context, spaceId, ownerId string, root cid.Cid) (fileId string, ok bool, err error)
ResolveIntent is the gc.RowResolver heal hook: find a live row of ownerId whose rootCid is root (the attach crash window left the row without its CAR ref).
func (*Service) ResumePendingJoins ¶
func (s *Service) ResumePendingJoins()
ResumePendingJoins triggers an immediate scan for joining rows that lack a waiter — joins left pending from a previous session, and rows synced in from the account's other devices. Called by sdk.Open after the tech space is open (New runs, and starts the controller, before tsp.Open, so the controller's own initial pass races the open and finds an empty list) and on every tech-space index change, so a join requested on another device gets its waiter here promptly instead of on the next tick.
func (*Service) RunFileJob ¶
RunFileJob executes one queue job (the status.Runner wired by sdk.Open). A job whose space or row disappeared (deleted) succeeds vacuously so the queue drops it — it must never resurrect a deleted space's storage by loading it. An ended join whose storage still exists (the accept-vs-cancel race) is not a delete: its jobs wait for the join controller's reload, Get refusing the row meanwhile.
func (*Service) SetDevice ¶
SetDevice upserts this device's row in the devices registry (see space.Service.SetDevice for the contract). Thin wrapper — the techspace method owns the peer-id resolution and op encoding.
func (*Service) SetFiles ¶
func (s *Service) SetFiles(up *upload.Service, fe *fetch.Service, st *filestore.Store, q *status.Queue)
SetFiles wires the SDK-level files byte-layer services. Called once from sdk.Open before any Space handle is handed out.
func (*Service) SetOnSpaceBorn ¶
SetOnSpaceBorn registers the successful-Create/Derive hook (see the onSpaceBorn field). Called once from sdk.Open.
func (*Service) SetReadSync ¶
SetReadSync injects the SDK-level read-state sync service. Called once from sdk.Open after the tech space is up.
func (*Service) SetSettings ¶
func (s *Service) SetSettings(ctx context.Context, spaceId string, set map[string]any, unset []string) error
SetSettings patches the per-space client settings on spaceId's tech-space row (see space.Service.SetSettings for the contract). The row must exist: the techspace write is a strict (non-upsert) modify — on an absent id it would silently no-op — so the unknown-id case is turned into an error here. Deleted rows pass: the handler's terminal-delete rule guards only status fields, and a tombstone's settings staying editable is deliberate (the row remains the account's record of the space).
func (*Service) ShouldPullTree ¶
func (s *Service) ShouldPullTree(ctx context.Context, spaceId, treeId string, root *treechangeproto.RawTreeChangeWithId, heads []string) bool
ShouldPullTree is the selective-sync pull decision for a locally-missing tree announced by a head update. The tech space is always fully synced; regular spaces delegate to their Store, which classifies by the update's root changeType and, when declining, refreshes the tree's heads-only stub so the sync diff converges.
func (*Service) SpaceFiles ¶
SpaceFiles is the gc.RowResolver bulk surface: fileId → durable for every live file row of the space, one pass over its payloads objects. An unloadable space is an error — GC retains on it.
func (*Service) SpaceIndexObjectId ¶
SpaceIndexObjectId returns the well-known id of the tech-space index object. Pass it to Query/Subscribe to read the system datasets (spaces, profile, devices) generically. Future system objects expose their own ids the same way.
func (*Service) StartOneToOneInbox ¶
StartOneToOneInbox wires and starts the Layer-2 inbox subsystem: the receive notifier (coordinator push + poll → RegisterIncoming) and the send-retry loop. No-op when the inbox transport is unavailable (a coordinator-less deployment), leaving the out-of-band 1-1 path intact.
Called from sdk.Open AFTER the tech space is open — the receive handler and send loop both write the tech space, so they must not run before it is ready.
func (*Service) Status ¶
func (s *Service) Status(spaceId string) space.SpaceSyncStatus
Status returns a snapshot of spaceId's rolled-up sync state. Routes through the per-account syncstatus.Service on anysyncx.App.
func (*Service) StoreFor ¶
func (s *Service) StoreFor(spaceId string) *spaceobjects.Store
StoreFor returns the per-space spaceobjects.Store, building it on first access. Exposed for callers outside the package (e.g. the spacesync catch-up driver invoked from SDK.Open) that need the store handle without going through Get / Create / Derive.
func (*Service) Subscribe ¶
func (s *Service) Subscribe(cb func(space.SpaceListEvent)) (cancel func())
Subscribe delivers live space-list deltas. It registers a sub on the tech-space index object's `spaces` dataset (which now projects inbound head-sync changes live, since the index object is Store-backed with a deferred-updater listener) and translates each engine event into a SpaceListEvent. Delta-only: callers seed current state via List.
Mapping: a row in any deleted shape (the synced tombstone, the 1-1 / guest offload markers, an ended join) surfaces under Removed regardless of whether the engine classified it Added/Updated; everything else maps to Added/Updated as the engine saw it. The cancel func stops the drain goroutine and closes the sub.
func (*Service) SubscribeStatus ¶
func (s *Service) SubscribeStatus(cb func(space.SpaceSyncStatus)) (cancel func())
SubscribeStatus registers cb for SpaceSyncStatus events across every known space. Account-wide firehose backed by the syncstatus Service's subscriber registry.
func (*Service) SweepOrphanCollections ¶
SweepOrphanCollections is the startup orphan-collection GC: it drops every SDK CRDT collection whose owner — the `<spaceId>_` or `<objectId>_` name prefix — is positively dead: a tombstoned (deleted/offloaded) space, or an object no kept space claims. Offload and object purge are best-effort, so a crash mid-teardown (or a deletion from a build that predated the per-object drop) can leak per-object collections in the shared DB forever; this sweep turns every such leak, past or future, into a self-healing condition.
A space's tech-space row is never physically removed (sticky tombstone), so a deleted space is always enumerable — no positive tombstone means keep, however orphaned a shell may look.
Call once at SDK open, after the tech space is up and BEFORE any other space loads — with no applies running, the liveness reads below can't race an in-flight object materialization. This is why the sweep stays synchronous in Open while the eager space-loading loop runs on the background bootstrap pass: only the pre-return, pre-bootstrap spot guarantees "no space loads, no caller holds the handle", and the sweep is cheap (1.6-40ms measured). Best-effort: a failed sweep is logged and boot continues.
The sweep is scoped to one DB: names, rosters and meta rows are all read from s.db. If storage ever moves to per-space DBs (dbRouter, docs/data-structure.md § Storage Topology), space offload becomes a file delete and this same per-DB pass — run against each space DB — keeps healing object-purge leaks inside live spaces.
func (*Service) SyncSpaceList ¶
SyncSpaceList forces an immediate head-sync round on the tech space so spaces added or removed on other devices land in the local index without waiting for the periodic headsync timer. Call before List to converge on demand.
func (*Service) Track ¶
Track registers a foreign spaceId in the tech-space index so a later Get can open it. The row is the Create/Join registry shape minus the membership semantics — Type unknown until the first load backfills it from the header, remote+local status active, no metadata (nothing is known about a space we hold no keys to; the name/icon mirror only runs for materialized spaceIndex trees).
No network round trip happens here: Get is what triggers any-sync's storage bootstrap (SpacePull from the responsible nodes when local storage is missing). Idempotent; an existing row of any status — own, joined, tracked, or deleted — is left untouched.
func (*Service) WaitListSynced ¶
WaitListSynced blocks until the tech space completes a clean head-sync round with no parked trees (same convergence test as inboxReplayGuard), retrying rounds until ctx expires. SyncHeads no-ops (nil) on a not-open tech space — that must read as "not ready", not as a clean round. A nil round alone is NOT proof of convergence — any-sync's diffsyncer swallows per-peer sync failures and returns nil — so the gate also requires the tracker rollup to report Synced: that flips only on HeadsApply from a responsible peer, stays Syncing on a swallowed failure, and reads Offline with no peers, which is exactly the "empty list because nothing was fetched" case this gate must not bless.
Source Files
¶
- accountmirror.go
- acl.go
- aclkick.go
- aclmirror_watcher.go
- aggregate.go
- bundles.go
- changeindex.go
- collections.go
- custody.go
- debug.go
- deletioncontroller.go
- doc.go
- files.go
- files_status.go
- gc.go
- history.go
- identities_api.go
- joincontroller.go
- members.go
- members_collection.go
- members_query.go
- objects.go
- offload.go
- onetoone_inbox.go
- onetoone_keys.go
- payloads.go
- payloads_view.go
- properties.go
- pubsub.go
- push.go
- query.go
- readstate.go
- regularinvite.go
- result.go
- service.go
- space.go
- spaceindex.go
- spaceindex_watcher.go
- spacetype.go
- syncstatus.go
- techspace_handle.go
- track.go
- typedatasets.go
- types.go
- unsupported.go
- upsert.go
- watchers.go