connectors

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Azure Blob Storage connector: shared-key auth (an account name + account key, an admin pastes in once), the same static-credential shape as S3 - not SAS tokens or Entra ID/OAuth, deliberately (see this connector's entry in ROADMAP.md for why: this is an admin-configured infrastructure credential, not a per-employee identity, so the OAuth machinery SharePoint needs doesn't apply here).

Like S3, Azure's flat blob listing has no "changes" API to page through - Sync's cursor is an RFC3339Nano timestamp, same full-list-then-filter approach as S3Connector.Sync.

Confluence connector: Confluence Cloud (read-only, pages only) via Atlassian's REST APIs. Structurally different from every other OAuth connector in this package in two real ways, not cosmetic ones:

  • Atlassian rotates refresh tokens on every use (the old one is invalidated the instant a new one is issued) - unlike Google/ Microsoft's long-lived, reusable refresh tokens, which is why every other connector here can get away with never persisting a refreshed token back to the caller's store. This connector proactively refreshes once per call and records the result on lastRefreshed - a deliberate, contained stateful exception to this package's otherwise stateless-per-call connector shape - so a caller can persist the rotated pair via RefreshedToken() before the next call needs it.
  • There is no fixed API base URL: every call is scoped under a per-account cloud ID, resolved once per call via Atlassian's accessible-resources endpoint (see confluence_atlassian.go).

No native delta/changes API exists (unlike SharePoint/OneDrive's Graph delta) - Sync uses Confluence's CQL search sorted by lastmodified desc, stopping once it reaches the cursor boundary, the same "poll and refilter by modified-since" shape S3 already uses for the same reason.

Google Drive connector: Google Drive (read-only) via the Drive API v3.

Notion connector: Notion workspaces (read-only, pages only) via Notion's REST API. No official Go SDK is vendored in this module (matching sharepoint_graph.go/confluence_atlassian.go's own reasoning) - a small hand-rolled client sending the required Notion-Version header on every call.

No native delta/changes API exists, and no per-page sharing/ACL endpoint either - unlike Drive, an integration only ever sees what it's been explicitly shared with, and there's no way to enumerate individual viewers of a given page, so SyncItem.AccessControl stays nil here, the same accepted precedent as S3/Azure Blob/Confluence.

Unlike Confluence, there's no per-account "cloud ID" (Notion has one fixed base URL) and no refresh-token rotation to track (Notion's OAuth tokens aren't rotated per call) - structurally closer to gdrive.go/ sharepoint.go there.

OneDrive for Business connector: a single employee's own personal drive via Microsoft Graph, reusing sharepoint_graph.go's spGraphClient (its delta/download/permissions primitives are keyed only by an opaque driveID, with no SharePoint-specific coupling) and sharepoint.go's shared graphSyncOneItem/spIsAllowedFile/spDriveItemToResourceRef. The only real difference from SharePointConnector: this connector always has exactly one drive (GET /me/drive), never a site to enumerate, so its cursor is a plain delta-link string rather than SPDriveCursor's per-drive map.

S3 connector: any S3-compatible object store (AWS S3, MinIO, Cloudflare R2, Backblaze B2 - anything speaking the S3 REST API against a configurable endpoint).

Unlike Drive/SharePoint, S3 has no "changes" API to page through - Sync's cursor is instead an RFC3339Nano timestamp: a full ListObjectsV2 pass every time, filtered to objects whose LastModified is after the cursor.

SharePoint connector: SharePoint Online (read-only) via Microsoft Graph. Structurally mirrors gdrive.go, with two differences both because SharePoint/Graph works differently, not because this connector cuts corners:

  • No native-format export step: Office files (.docx/.pptx/.xlsx) are real binaries and download directly — there's no SharePoint equivalent of a Google Doc with no native binary form.
  • Delta (change tracking) is per-drive, not account-wide — a SharePoint account can see many drives (one per site's document library), each with its own delta link, so the sync cursor is a small JSON map (driveID -> deltaLink) instead of Drive's single startPageToken. See SPDriveCursor.

Package connectors is the tenant-agnostic core of SeshatOS's Knowledge connectors: the Google Drive, SharePoint, and S3-compatible Discover/Sync logic, extracted out of both seshat-backend (internal/knowledge/gdrive, internal/knowledge/sharepoint, internal/knowledge/s3) and seshat-server (internal/server/connectors) after both had independently ported the same ~1,700 lines of API-client/permission-mapping/extension-allowlist code - see ROADMAP.md's "core/connectors" entry for the extraction and why it mirrors core/prospecting's shape (a small sibling Go module shared via go.work, not imported by either backend directly into the other).

Deliberately does not export an Account type or a shared connector interface: neither is needed. Grepping both original sides confirmed Discover/Sync never used anything from the caller's account struct except S3's own Config string (bucket/region/endpoint/prefix) - so each connector's methods take exactly what they use (Secret, plus an explicit config string for S3 only), not a generic Account shape. Each side keeps its own connector-selection interface (connector.KnowledgeConnector in seshat-backend, knowledgeConnector in seshat-server) and its own thin wrapper adapting that interface to these concrete types - the two interfaces' shapes already differed slightly before this extraction, and unifying them isn't this package's job.

OAuth flow orchestration (state/PKCE/token exchange/app credentials) also stays on each side, since it's genuinely different per side (seshat-backend: one shared global client; seshat-server: a per-organization app registry) - only the scope lists a connector actually needs (GDriveScopes/ SharePointScopes) are shared here, since both sides were duplicating the same string literals.

Index

Constants

This section is empty.

Variables

View Source
var ConfluenceScopes = []string{
	"read:page:confluence",
	"read:space:confluence",
	"read:me",
	"offline_access",
}

ConfluenceScopes requested from Atlassian - read-only page/space access, identity (to resolve the connected account's email), and offline_access for a refresh token. Verified live against Atlassian's granular-scopes docs and the User Identity API docs before implementing.

View Source
var GDriveScopes = []string{
	"https://www.googleapis.com/auth/drive.readonly",
	"https://www.googleapis.com/auth/userinfo.email",
}

GDriveScopes requested from Google - read-only Drive access plus the account's email (used by callers as the connector account's external identity). Read-only by design: this is a Knowledge connector, never writes back to Drive.

View Source
var OneDriveScopes = []string{
	"openid",
	"profile",
	"offline_access",
	"https://graph.microsoft.com/Files.Read.All",
}

OneDriveScopes requested from Microsoft - read-only Files access plus openid/profile/offline_access (the last is required to receive a refresh token). No Sites.Read.All - this connector never touches a SharePoint site, only the connected user's own personal drive.

View Source
var SharePointScopes = []string{
	"openid",
	"profile",
	"offline_access",
	"https://graph.microsoft.com/Files.Read.All",
	"https://graph.microsoft.com/Sites.Read.All",
}

SharePointScopes requested from Microsoft - read-only Sites/Files access plus openid/profile/offline_access (the last is required to receive a refresh token, Microsoft's equivalent of Google's access_type=offline). Read-only by design: this is a Knowledge connector, never writes back to SharePoint.

Functions

func EncodeAzureBlobAccountConfig

func EncodeAzureBlobAccountConfig(accountName, container, prefix string) (string, error)

EncodeAzureBlobAccountConfig is ParseAzureBlobAccountConfig's inverse, for a caller's service layer to build the config string value it stores at connect time.

func EncodeS3AccountConfig

func EncodeS3AccountConfig(bucket, region, endpoint, prefix string) (string, error)

EncodeS3AccountConfig is ParseS3AccountConfig's inverse, for a caller's service layer to build the config string value it stores at connect time.

func SetNotionBaseURLForTesting

func SetNotionBaseURLForTesting(baseURL string) (restore func())

SetNotionBaseURLForTesting points this package's Notion API base URL at a test server, returning a function that restores the real value. Never call this outside a test.

func SetServiceURLForTesting

func SetServiceURLForTesting(build func(accountName string) string) (restore func())

SetServiceURLForTesting points this package's Azure Blob service URL builder at a test server (e.g. a local Azurite emulator), returning a function that restores the real value. Never call this outside a test.

Types

type AccessEntry

type AccessEntry string

AccessEntry names one identity or group allowed to see a resource ("user:alice@example.com", "group:eng-team", "domain:example.com", or "public"), mirroring Elastic Connectors' prefixed-identity ACL model. A nil or empty AccessControl on a SyncItem means "inherit the caller's own coarser scope" - neither connector over-shares when it can't resolve a permission to a concrete identity.

type AzureBlobAccountConfig

type AzureBlobAccountConfig struct {
	AccountName string `json:"account_name"`
	Container   string `json:"container"`
	Prefix      string `json:"prefix"`
}

AzureBlobAccountConfig is the non-secret connection config for one connected account - account name/container/prefix aren't secrets, so they don't belong in Secret. Secret.AccessToken holds the storage account's shared key; Secret.RefreshToken stays unused - shared-key auth needs only one secret, unlike S3's access-key-ID/secret-access-key pair.

func ParseAzureBlobAccountConfig

func ParseAzureBlobAccountConfig(config string) (AzureBlobAccountConfig, error)

ParseAzureBlobAccountConfig decodes a config string into its Azure Blob config - used by a caller's service layer to validate a connect request's config shape using the same rules Discover/Sync will apply.

type AzureBlobConnector

type AzureBlobConnector struct {
	// contains filtered or unexported fields
}

AzureBlobConnector implements Azure Blob Storage Discover/Sync.

func NewAzureBlobConnector

func NewAzureBlobConnector() *AzureBlobConnector

NewAzureBlobConnector creates an Azure Blob Storage connector.

func (*AzureBlobConnector) Discover

func (c *AzureBlobConnector) Discover(ctx context.Context, config string, secret Secret) ([]ResourceRef, error)

Discover lists blobs in the account's container without downloading content - a lightweight preview of what a Sync would pull in. config is the JSON string ParseAzureBlobAccountConfig/EncodeAzureBlobAccountConfig round-trip.

func (*AzureBlobConnector) Sync

func (c *AzureBlobConnector) Sync(ctx context.Context, config string, secret Secret, cursor string) ([]SyncItem, string, error)

Sync bootstraps (cursor == "") by listing and downloading every allowed blob, or (cursor != "", an RFC3339Nano timestamp) re-lists everything but only downloads blobs modified after the cursor - Azure's flat blob list has no change-feed API either, matching S3Connector.Sync's exact reasoning. The returned cursor is captured before listing starts so a blob modified mid-sync isn't missed by the next incremental run.

func (*AzureBlobConnector) WithTextExtractor

func (c *AzureBlobConnector) WithTextExtractor(fn TextExtractorFunc) *AzureBlobConnector

WithTextExtractor sets the function that turns a downloaded binary file into text (see TextExtractorFunc) and returns the connector, for chaining.

type ConfluenceConnector

type ConfluenceConnector struct {
	// contains filtered or unexported fields
}

ConfluenceConnector implements Confluence Cloud Discover/Sync.

func NewConfluenceConnector

func NewConfluenceConnector(oauthConfig *oauth2.Config) *ConfluenceConnector

NewConfluenceConnector creates a Confluence connector that authenticates with the given OAuth configuration.

func (*ConfluenceConnector) Discover

func (c *ConfluenceConnector) Discover(ctx context.Context, secret Secret) ([]ResourceRef, error)

Discover lists every page the connected account can see, without downloading content - a lightweight preview, mirroring SharePointConnector.Discover.

func (*ConfluenceConnector) RefreshedToken

func (c *ConfluenceConnector) RefreshedToken() (accessToken, refreshToken string, expiry time.Time, ok bool)

RefreshedToken reports the rotated credential pair from the most recent Discover/Sync call, if that call actually triggered a token refresh - ok=false means the stored refresh token is still the current one and nothing needs persisting.

func (*ConfluenceConnector) Sync

func (c *ConfluenceConnector) Sync(ctx context.Context, secret Secret, cursor string) ([]SyncItem, string, error)

Sync bootstraps (cursor == "") or continues (cursor is an RFC3339Nano timestamp, same shape as S3's) - no change-feed API exists, so incrementality here means "search sorted newest-first, stop once past the cursor boundary" rather than resuming a change stream. The returned cursor is captured before the search starts, so a page modified mid-sync isn't missed by the next incremental run.

func (*ConfluenceConnector) WithTextExtractor

func (c *ConfluenceConnector) WithTextExtractor(fn TextExtractorFunc) *ConfluenceConnector

WithTextExtractor sets the function that turns a downloaded binary file into text (see TextExtractorFunc) and returns the connector, for chaining.

type GDriveConnector

type GDriveConnector struct {
	// contains filtered or unexported fields
}

GDriveConnector implements Google Drive Discover/Sync.

func NewGDriveConnector

func NewGDriveConnector(oauthConfig *oauth2.Config) *GDriveConnector

NewGDriveConnector creates a Google Drive connector that authenticates with the given OAuth configuration.

func (*GDriveConnector) Discover

func (c *GDriveConnector) Discover(ctx context.Context, secret Secret) (refs []ResourceRef, err error)

Discover lists files in the account's Drive without fetching their content - a lightweight preview of what a Sync would pull in.

func (*GDriveConnector) RefreshPermissions

func (c *GDriveConnector) RefreshPermissions(ctx context.Context, secret Secret, resourceIDs []string) (map[string][]AccessEntry, error)

RefreshPermissions re-fetches the current Drive ACL for a batch of already-known file IDs, without re-fetching content - lets a caller keep CorpusFile.ExternalAccess fresh on its own (typically shorter) schedule, independent of content re-sync.

Deliberately does NOT reuse fetchAccessControl, whose "swallow the error, return nil" contract is right for first-sync (an unknown file defaults to corpus-level access, never blocked) but wrong here: on a refresh pass, collapsing "API call failed" into the same nil/empty result as "confirmed zero restrictions" would let a transient error silently WIDEN access instead of just failing to narrow it. A file whose lookup fails here is omitted from the returned map entirely - the caller should leave that file's last-known ExternalAccess untouched rather than risk widening it.

func (*GDriveConnector) Sync

func (c *GDriveConnector) Sync(ctx context.Context, secret Secret, cursor string) (items []SyncItem, nextCursor string, err error)

Sync bootstraps (cursor == "") by listing and downloading every file, or pulls only what changed since cursor via Drive's Changes API.

func (*GDriveConnector) WithEndpoint

func (c *GDriveConnector) WithEndpoint(baseURL string) *GDriveConnector

WithEndpoint points the Drive client at another server instead of Google, so the connector can be exercised against a recorded or fake Drive, or a Google-compatible proxy. baseURL is the whole API base, including the version path (for example "http://127.0.0.1:8080/drive/v3/"). Returns the receiver so it can be chained onto NewGDriveConnector.

func (*GDriveConnector) WithTextExtractor

func (c *GDriveConnector) WithTextExtractor(fn TextExtractorFunc) *GDriveConnector

WithTextExtractor wires binary-file extraction into downloadText - see TextExtractorFunc's doc comment. Returns the receiver so callers can chain it onto NewGDriveConnector.

type NotionConnector

type NotionConnector struct {
	// contains filtered or unexported fields
}

NotionConnector implements Notion Discover/Sync.

func NewNotionConnector

func NewNotionConnector(oauthConfig *oauth2.Config) *NotionConnector

NewNotionConnector creates a Notion connector that authenticates with the given OAuth configuration.

func (*NotionConnector) Discover

func (c *NotionConnector) Discover(ctx context.Context, secret Secret) ([]ResourceRef, error)

Discover lists every page the connected integration can see, without downloading content - a lightweight preview, mirroring ConfluenceConnector.Discover.

func (*NotionConnector) Sync

func (c *NotionConnector) Sync(ctx context.Context, secret Secret, cursor string) ([]SyncItem, string, error)

Sync bootstraps (cursor == "") or continues (cursor is a TimeCursor, types.go) - no change-feed API exists, so incrementality here means "search sorted newest-edited-first, stop once past the cursor boundary," the same shape confluence.go/s3.go use for the same reason. The returned cursor is captured before the search starts, so a page edited mid-sync isn't missed by the next incremental run.

func (*NotionConnector) WithTextExtractor

func (c *NotionConnector) WithTextExtractor(fn TextExtractorFunc) *NotionConnector

WithTextExtractor sets the function that turns a downloaded binary file into text (see TextExtractorFunc) and returns the connector, for chaining.

type OneDriveConnector

type OneDriveConnector struct {
	// contains filtered or unexported fields
}

OneDriveConnector implements OneDrive for Business Discover/Sync.

func NewOneDriveConnector

func NewOneDriveConnector(oauthConfig *oauth2.Config) *OneDriveConnector

NewOneDriveConnector creates a OneDrive connector that authenticates with the given OAuth configuration.

func (*OneDriveConnector) Discover

func (c *OneDriveConnector) Discover(ctx context.Context, secret Secret) ([]ResourceRef, error)

Discover lists files in the connected account's own OneDrive, without downloading content - a lightweight preview, mirroring SharePointConnector.Discover. Always a fresh full enumeration (deltaPage with no stored link), never touching a stored sync cursor.

func (*OneDriveConnector) Sync

func (c *OneDriveConnector) Sync(ctx context.Context, secret Secret, cursor string) ([]SyncItem, string, error)

Sync bootstraps (cursor == "") or continues (cursor holds the drive's own delta link) - unlike SharePoint there is only ever one drive, so the cursor is that drive's plain delta-link string, not a per-drive map.

func (*OneDriveConnector) WithTextExtractor

func (c *OneDriveConnector) WithTextExtractor(fn TextExtractorFunc) *OneDriveConnector

WithTextExtractor sets the function that turns a downloaded binary file into text (see TextExtractorFunc) and returns the connector, for chaining.

type RateLimited

type RateLimited interface {
	error
	RetryAfter() (time.Duration, bool)
}

RateLimited is implemented by an error a connector returns to represent a rate-limit/backpressure response (HTTP 429, or a provider-specific quota-exceeded signal) rather than a genuine failure - a caller decides how long to back off by checking for this via errors.As instead of applying a flat retry delay regardless of cause. ok=false from RetryAfter means "this was rate-limiting, but no specific delay was suggested" - the caller should fall back to its own default, not treat it as "not rate-limited."

type ResourceRef

type ResourceRef struct {
	ID       string
	Name     string
	Kind     string // "file", "folder", "record", ...
	Modified time.Time
}

ResourceRef is what Discover returns per resource found - enough to decide whether/how to sync it without fetching its content yet.

type S3AccountConfig

type S3AccountConfig struct {
	Bucket   string `json:"bucket"`
	Region   string `json:"region"`
	Endpoint string `json:"endpoint"`
	Prefix   string `json:"prefix"`
}

S3AccountConfig is the non-secret connection config for one connected account - bucket/region/endpoint/prefix aren't secrets, so they don't belong in Secret. Secret.AccessToken holds the S3 access key ID, RefreshToken the secret access key - a deliberate reinterpretation of those two generically-named fields, not a naming mistake.

func ParseS3AccountConfig

func ParseS3AccountConfig(config string) (S3AccountConfig, error)

ParseS3AccountConfig decodes a config string into its S3 config - used by a caller's service layer to validate a connect request's config shape using the same rules Discover/Sync will apply.

type S3Connector

type S3Connector struct {
	// contains filtered or unexported fields
}

S3Connector implements S3-compatible Discover/Sync.

func NewS3Connector

func NewS3Connector() *S3Connector

NewS3Connector creates a connector for S3-compatible storage.

func (*S3Connector) Discover

func (c *S3Connector) Discover(ctx context.Context, config string, secret Secret) ([]ResourceRef, error)

Discover lists objects in the account's bucket without downloading content - a lightweight preview of what a Sync would pull in. config is the JSON string ParseS3AccountConfig/EncodeS3AccountConfig round-trip.

func (*S3Connector) Sync

func (c *S3Connector) Sync(ctx context.Context, config string, secret Secret, cursor string) ([]SyncItem, string, error)

Sync bootstraps (cursor == "") by listing and downloading every allowed object, or (cursor != "", an RFC3339Nano timestamp) re-lists everything but only downloads objects modified after the cursor - S3 has no change-feed API, so incrementality here means "re-list, filter by LastModified" rather than "resume a change stream". The returned cursor is captured before listing starts so an object modified mid-sync isn't missed by the next incremental run.

func (*S3Connector) WithTextExtractor

func (c *S3Connector) WithTextExtractor(fn TextExtractorFunc) *S3Connector

WithTextExtractor sets the function that turns a downloaded binary file into text (see TextExtractorFunc) and returns the connector, for chaining.

type SPDriveCursor

type SPDriveCursor map[string]string

SPDriveCursor is the opaque per-drive delta-link map a caller persists as its own sync cursor between calls - see the package doc comment for why it's a map rather than one account-wide token. Callers of Sync never need to know its shape beyond encoding/decoding it as a plain string.

func DecodeSPDriveCursor

func DecodeSPDriveCursor(raw string) SPDriveCursor

DecodeSPDriveCursor parses a cursor produced by Encode. An empty or invalid string gives an empty cursor.

func (SPDriveCursor) Encode

func (c SPDriveCursor) Encode() string

Encode returns the cursor as the JSON string a caller persists between syncs, or an empty JSON object when it cannot be encoded.

type Secret

type Secret struct {
	AccessToken  string
	RefreshToken string
	ExpiresAt    time.Time
}

Secret is the decrypted connection material a connector needs to call its external API. For gdrive/sharepoint these are OAuth access/refresh tokens; for S3, AccessToken/RefreshToken are reinterpreted as the access key ID and secret access key (a deliberate reuse of the generic field names, not a naming mistake - see S3Connector's doc comment).

type SharePointConnector

type SharePointConnector struct {
	// contains filtered or unexported fields
}

SharePointConnector implements SharePoint Discover/Sync.

func NewSharePointConnector

func NewSharePointConnector(oauthConfig *oauth2.Config) *SharePointConnector

NewSharePointConnector creates a SharePoint connector that authenticates with the given OAuth configuration.

func (*SharePointConnector) Discover

func (c *SharePointConnector) Discover(ctx context.Context, secret Secret) ([]ResourceRef, error)

Discover lists files across every site/drive the connected account can access, without downloading content - a lightweight preview, mirroring GDriveConnector.Discover. Always does a fresh full enumeration per drive (deltaPage with no stored link), never touching a stored sync cursor.

func (*SharePointConnector) Sync

func (c *SharePointConnector) Sync(ctx context.Context, secret Secret, cursor string) ([]SyncItem, string, error)

Sync bootstraps (cursor == "") or continues (cursor holds a per-drive delta-link map, see SPDriveCursor) - every drive the account can currently see is visited every call. A drive with no stored delta link yet gets a full enumeration just for that one drive, exactly like Discover; a drive that already has one only pulls what changed.

func (*SharePointConnector) WithTextExtractor

func (c *SharePointConnector) WithTextExtractor(fn TextExtractorFunc) *SharePointConnector

WithTextExtractor sets the function that turns a downloaded binary file into text (see TextExtractorFunc) and returns the connector, for chaining.

type SyncItem

type SyncItem struct {
	ResourceRef
	Data          []byte
	ContentType   string
	Text          string
	AccessControl []AccessEntry
}

SyncItem is what Sync yields per resource - content ready for ingestion, plus the access control list it should carry.

type TextExtractorFunc

type TextExtractorFunc func(ctx context.Context, data []byte, contentType, filename string) string

TextExtractorFunc converts a downloaded binary file's raw bytes into ingestable text (e.g. PDF/DOCX/PPTX via a document reader) - nil means "no extractor wired", and each connector falls back to treating raw bytes as text, which is wrong for binary formats. Wired via each connector type's WithTextExtractor, normally to the caller's own document-reader extraction so connector-synced content goes through the same path as a manual upload.

type TimeCursor

type TimeCursor struct {
	Since time.Time
}

TimeCursor is the shared sync-cursor shape for a connector with no native change-feed API (s3.go, azureblob.go, confluence.go, today) - each of those instead resyncs by listing everything and filtering to whatever changed after a timestamp, so "the cursor" is just "when the previous sync started." Was three independent, byte-for-byte identical RFC3339Nano parse/format implementations before being extracted here; mirrors SPDriveCursor's existing Decode.../Encode() shape (sharepoint.go) for a connector kind that does have real structure to a cursor.

func DecodeTimeCursor

func DecodeTimeCursor(cursor string) (TimeCursor, error)

DecodeTimeCursor parses cursor as RFC3339Nano - not RFC3339, which truncates to whole seconds and can lose real ordering between a resource's own modified time and this cursor landing in the same second. Empty means "no cursor yet" (Since stays the zero time.Time, so (TimeCursor{}).Since.IsZero() is how a caller detects "bootstrap sync").

func NewTimeCursorNow

func NewTimeCursorNow() TimeCursor

NewTimeCursorNow is the "next cursor" a bootstrap or incremental sync returns - capture it once, before listing starts, so a resource modified mid-sync isn't missed by the following incremental run.

func (TimeCursor) Encode

func (c TimeCursor) Encode() string

Encode formats c back into the string form ConnectorAccount.SyncCursor stores.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL