@spacelr/sdk 0.4.0 → 0.5.1

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/README.md CHANGED
@@ -42,9 +42,41 @@ spacelr.db.subscribe('my-collection', {
42
42
  | --- | --- |
43
43
  | `auth` | Login, registration, OAuth2 PKCE flow, token management, 2FA |
44
44
  | `storage` | File upload (including multipart), download, sharing, quota |
45
- | `db` | Database operations with realtime subscriptions via WebSocket |
45
+ | `db` | Database operations with realtime subscriptions via WebSocket; `db.timeline` for cold-tier history reads |
46
46
  | `notifications` | Web Push notification subscription management |
47
47
 
48
+ ## Cold-tier history (`db.timeline`)
49
+
50
+ Collections can enable **cold-tier** archival: aged documents are moved to
51
+ object storage and purged from the live (hot) MongoDB tier. The normal query
52
+ methods (`find`, `findById`, `count`, `search`, `paginate`) operate on the **hot
53
+ tier only** — archived documents will not appear in their results.
54
+
55
+ To read archived history, use the timeline API, which transparently merges hot
56
+ and cold data for a partition (e.g. a chat room) and paginates with an opaque
57
+ cursor:
58
+
59
+ ```ts
60
+ const page = await spacelr.db.timeline.query({
61
+ collection: 'chat_messages',
62
+ partitionValue: 'room-123', // the cold-tier partition (e.g. room id)
63
+ where: { authorId: { $eq: 'user-1' } }, // optional, allow-listed operators only
64
+ orderBy: { field: 'createdAt', direction: 'desc' },
65
+ limit: 50, // 1–200, default 50
66
+ });
67
+
68
+ page.items; // merged hot + cold documents
69
+ page.nextCursor; // pass back as `cursor` for the next page (null = end)
70
+ page.sourceStats; // { hot, cold, segmentsScanned? } — where the rows came from
71
+ ```
72
+
73
+ Notes:
74
+ - Timeline paginates by partition + timestamp, not arbitrary Mongo queries;
75
+ `where` accepts only the allow-listed operators (`$eq`, `$ne`, `$lt`, `$lte`,
76
+ `$gt`, `$gte`, `$in`, `$nin`, `$and`).
77
+ - Enabling/configuring cold-tier on a collection is an **admin** operation and
78
+ is not part of this client SDK.
79
+
48
80
  ## Requirements
49
81
 
50
82
  - Node.js >= 18
package/dist/index.d.mts CHANGED
@@ -738,6 +738,98 @@ declare class StorageModule {
738
738
  getPublicFileUrl(fileId: string, projectId?: string): Promise<string>;
739
739
  }
740
740
 
741
+ /**
742
+ * Scalar values permitted inside a timeline filter. Matches the server
743
+ * filter allow-list (libs/shared-dtos/.../timeline-filter.dto.ts).
744
+ */
745
+ type TimelineScalar = string | number | boolean | null;
746
+ /**
747
+ * Single-field filter expression. Either implicit equality (a scalar) or
748
+ * an explicit operator object. Mongo-style allow-list — the server rejects
749
+ * anything outside this set.
750
+ */
751
+ type TimelineLeafFilter = TimelineScalar | {
752
+ $eq?: TimelineScalar;
753
+ $ne?: TimelineScalar;
754
+ $lt?: TimelineScalar;
755
+ $lte?: TimelineScalar;
756
+ $gt?: TimelineScalar;
757
+ $gte?: TimelineScalar;
758
+ $in?: TimelineScalar[];
759
+ $nin?: TimelineScalar[];
760
+ };
761
+ /**
762
+ * Filter document for a timeline query. Top-level fields apply implicit
763
+ * equality (or operator expression). $and supports nested filter
764
+ * composition; $or / $regex / etc. are intentionally NOT exposed and
765
+ * will be rejected server-side.
766
+ *
767
+ * Two named branches in the union so TypeScript doesn't treat `$and` as
768
+ * just another key under the field index signature — the array shape
769
+ * needs its own type or downstream usage breaks.
770
+ */
771
+ interface TimelineAndFilter {
772
+ $and: TimelineFilter[];
773
+ }
774
+ type TimelineFieldFilter = Record<string, TimelineLeafFilter>;
775
+ type TimelineFilter = TimelineAndFilter | TimelineFieldFilter;
776
+ interface TimelineOrderBy {
777
+ field: string;
778
+ direction: 'asc' | 'desc';
779
+ }
780
+ interface TimelineQueryOptions {
781
+ /** Collection name to read from. */
782
+ collection: string;
783
+ /** Partition value (e.g. roomId, documentId — collection-configured). */
784
+ partitionValue: string;
785
+ /** Optional filter constraints (allow-listed operators only). */
786
+ where?: TimelineFilter;
787
+ /**
788
+ * Sort order. Defaults to DESC by the collection's configured timeline
789
+ * field; PR A only supports DESC. Passing direction='asc' is reserved
790
+ * for a future phase and currently returns BAD_REQUEST.
791
+ */
792
+ orderBy?: TimelineOrderBy;
793
+ /** Page size (1..200). Default 50. */
794
+ limit?: number;
795
+ /**
796
+ * Opaque cursor from a previous response. The SDK does NOT decode this —
797
+ * pass the value of `nextCursor` from the last response verbatim.
798
+ */
799
+ cursor?: string;
800
+ }
801
+ interface TimelineSourceStats {
802
+ hot: number;
803
+ cold: number;
804
+ /** Number of cold archive segments scanned (only present when cold path ran). */
805
+ segmentsScanned?: number;
806
+ }
807
+ interface TimelineQueryResponse {
808
+ items: Record<string, unknown>[];
809
+ /** Opaque continuation token. `null` when the page is the last one. */
810
+ nextCursor: string | null;
811
+ sourceStats: TimelineSourceStats;
812
+ }
813
+
814
+ /**
815
+ * Low-level timeline SDK module. One method: query(opts) → page.
816
+ *
817
+ * The cursor is opaque from the SDK's perspective — it's stored as a
818
+ * string, never decoded, never inspected. Callers pass the value of
819
+ * `nextCursor` from the previous response verbatim into the next request.
820
+ *
821
+ * Errors thrown from query() extend SpacelrError so they integrate with
822
+ * the SDK's existing error hierarchy. Use one of the typed subclasses
823
+ * (CursorInvalidError, ForbiddenError, etc.) to catch specific cases or
824
+ * `catch (e instanceof TimelineError)` for any timeline-related failure.
825
+ */
826
+ declare class TimelineModule {
827
+ private readonly http;
828
+ constructor(http: HttpClient);
829
+ query(opts: TimelineQueryOptions): Promise<TimelineQueryResponse>;
830
+ private translateError;
831
+ }
832
+
741
833
  interface PopulateOption {
742
834
  field: string;
743
835
  collection: string;
@@ -1106,6 +1198,16 @@ declare class CollectionRef<T = Record<string, unknown>> {
1106
1198
  insertMany(documents: (Partial<T> & {
1107
1199
  _id?: string;
1108
1200
  })[]): Promise<InsertResult>;
1201
+ /**
1202
+ * Query the collection's **hot tier** (live MongoDB).
1203
+ *
1204
+ * **Cold-tier note:** if the collection has cold-tier archival enabled, aged
1205
+ * documents are moved to object storage and purged from the hot tier — they
1206
+ * will NOT appear in `find()` results (nor `findById`, `count`, `search`,
1207
+ * `paginate`, which are all hot-tier only). To read archived history, use
1208
+ * {@link TimelineModule.query} via `db.timeline.query(...)`, which transparently
1209
+ * merges hot and cold results for a partition.
1210
+ */
1109
1211
  find(filter?: Record<string, unknown>): QueryBuilder<T>;
1110
1212
  /**
1111
1213
  * Cursor-based scroll-back helper. Returns a `Paginator` whose `.next()`
@@ -1136,13 +1238,30 @@ declare class CollectionRef<T = Record<string, unknown>> {
1136
1238
  *
1137
1239
  * Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching
1138
1240
  * `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.
1241
+ *
1242
+ * **Cold-tier note:** searches the hot tier only; archived documents are not
1243
+ * included. See {@link CollectionRef.find} and `db.timeline.query(...)`.
1139
1244
  */
1140
1245
  search(opts: SearchOptions): Promise<SearchResult<T>>;
1246
+ /**
1247
+ * Fetch a single document by `_id` from the **hot tier**.
1248
+ *
1249
+ * **Cold-tier note:** returns null/throws for documents that have been
1250
+ * archived and purged from the hot tier. Archived history is reachable only
1251
+ * via `db.timeline.query(...)`. See {@link CollectionRef.find}.
1252
+ */
1141
1253
  findById(id: string, options?: FindByIdOptions): Promise<T & {
1142
1254
  _id: string;
1143
1255
  }>;
1144
1256
  update(id: string, update: Partial<T>): Promise<UpdateResult>;
1145
1257
  delete(id: string): Promise<DeleteResult>;
1258
+ /**
1259
+ * Count documents in the **hot tier** matching `filter`.
1260
+ *
1261
+ * **Cold-tier note:** counts hot-tier documents only — archived/purged
1262
+ * documents are not included, so this is not a total-history count. See
1263
+ * {@link CollectionRef.find} and `db.timeline.query(...)`.
1264
+ */
1146
1265
  count(filter?: Record<string, unknown>): Promise<number>;
1147
1266
  subscribe(handlers: SubscribeHandlers<T>): () => void;
1148
1267
  subscribeEvents(handlers: SubscribeEventsHandlers<T>): StreamSubscription;
@@ -1161,6 +1280,7 @@ declare class DatabaseModule {
1161
1280
  private http;
1162
1281
  private realtime;
1163
1282
  private projectId;
1283
+ readonly timeline: TimelineModule;
1164
1284
  constructor(http: HttpClient, projectId: string, realtime?: RealtimeClient);
1165
1285
  collection<T = Record<string, unknown>>(name: string): CollectionRef<T>;
1166
1286
  }
@@ -1269,12 +1389,127 @@ declare class FunctionsModule {
1269
1389
  invoke(projectId: string, functionId: string, options?: FunctionInvokeOptions): Promise<FunctionInvokeResult>;
1270
1390
  }
1271
1391
 
1392
+ type ScheduleStatus = 'scheduled' | 'fired' | 'failed' | 'cancelled';
1393
+ interface Schedule {
1394
+ id: string;
1395
+ projectId: string;
1396
+ functionId: string;
1397
+ payload: Record<string, unknown>;
1398
+ executeAt: string;
1399
+ idempotencyKey: string | null;
1400
+ status: ScheduleStatus;
1401
+ bullJobId: string | null;
1402
+ firedAt: string | null;
1403
+ executionId: string | null;
1404
+ attemptNumber: number;
1405
+ maxAttempts: number;
1406
+ lastError: string | null;
1407
+ createdAt: string;
1408
+ updatedAt: string;
1409
+ }
1410
+ interface ScheduleInvokeOptions {
1411
+ /** 24-char hex function id to invoke when the schedule fires. */
1412
+ functionId: string;
1413
+ /** Arbitrary JSON payload surfaced to the function as `event.payload`. */
1414
+ payload?: Record<string, unknown>;
1415
+ /**
1416
+ * When to fire. Accepts a `Date`, an ISO-8601 string, or epoch ms.
1417
+ * Server-side applies a 5-second past-skew tolerance and rejects anything
1418
+ * beyond `SCHEDULE_MAX_DELAY_DAYS` (default 90 days).
1419
+ */
1420
+ executeAt: Date | string | number;
1421
+ /**
1422
+ * Dedup key. Repeating the call with the same key in the same project for
1423
+ * the same function returns the existing schedule unchanged.
1424
+ */
1425
+ idempotencyKey?: string;
1426
+ /**
1427
+ * Maximum retry attempts after a failed execution (0–10, default 3).
1428
+ * Counts only retries — the initial fire is always attempted.
1429
+ */
1430
+ maxAttempts?: number;
1431
+ }
1432
+ interface ScheduleListOptions {
1433
+ functionId?: string;
1434
+ status?: ScheduleStatus;
1435
+ limit?: number;
1436
+ offset?: number;
1437
+ }
1438
+ declare class ScheduleModule {
1439
+ private readonly http;
1440
+ private readonly projectId;
1441
+ constructor(http: HttpClient, projectId: string);
1442
+ /**
1443
+ * Schedule a one-shot function invocation. Returns the existing handle
1444
+ * unchanged if `idempotencyKey` matches a prior invoke in this project
1445
+ * for this function.
1446
+ */
1447
+ invoke(options: ScheduleInvokeOptions): Promise<Schedule>;
1448
+ get(scheduleId: string): Promise<Schedule>;
1449
+ list(options?: ScheduleListOptions): Promise<{
1450
+ items: Schedule[];
1451
+ total: number;
1452
+ }>;
1453
+ cancel(scheduleId: string): Promise<Schedule>;
1454
+ private toIsoString;
1455
+ }
1456
+
1457
+ /**
1458
+ * Base class for all timeline-related errors thrown by the SDK. Extends
1459
+ * SpacelrError so it fits the SDK's existing `catch (e instanceof
1460
+ * SpacelrError)` pattern. Subclasses give callers fine-grained control
1461
+ * (e.g. catch only `CursorInvalidError` and restart pagination).
1462
+ */
1463
+ declare class TimelineError extends SpacelrError {
1464
+ constructor(message: string, opts?: {
1465
+ statusCode?: number;
1466
+ code?: string;
1467
+ });
1468
+ }
1469
+ declare class CursorInvalidError extends TimelineError {
1470
+ constructor(message: string, opts?: {
1471
+ statusCode?: number;
1472
+ code?: string;
1473
+ });
1474
+ }
1475
+ declare class ValidationError extends TimelineError {
1476
+ constructor(message: string, opts?: {
1477
+ statusCode?: number;
1478
+ code?: string;
1479
+ });
1480
+ }
1481
+ declare class ForbiddenError extends TimelineError {
1482
+ constructor(message: string, opts?: {
1483
+ statusCode?: number;
1484
+ code?: string;
1485
+ });
1486
+ }
1487
+ declare class NotFoundError extends TimelineError {
1488
+ constructor(message: string, opts?: {
1489
+ statusCode?: number;
1490
+ code?: string;
1491
+ });
1492
+ }
1493
+ declare class ServerConfigError extends TimelineError {
1494
+ constructor(message: string, opts?: {
1495
+ statusCode?: number;
1496
+ code?: string;
1497
+ });
1498
+ }
1499
+ declare class TimeoutError extends TimelineError {
1500
+ constructor(message: string, opts?: {
1501
+ statusCode?: number;
1502
+ code?: string;
1503
+ });
1504
+ }
1505
+
1272
1506
  interface SpacelrClient {
1273
1507
  readonly auth: AuthModule;
1274
1508
  readonly storage: StorageModule;
1275
1509
  readonly db: DatabaseModule;
1276
1510
  readonly notifications: NotificationsModule;
1277
1511
  readonly functions: FunctionsModule;
1512
+ readonly schedule: ScheduleModule;
1278
1513
  /**
1279
1514
  * Store auth tokens externally (e.g. obtained via the auth-components
1280
1515
  * library that bypasses the SDK). Makes the tokens available to HTTP +
@@ -1326,4 +1561,4 @@ interface SpacelrClient {
1326
1561
  }
1327
1562
  declare function createClient(config: SpacelrClientConfig): SpacelrClient;
1328
1563
 
1329
- export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type SearchOptions, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
1564
+ export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
package/dist/index.d.ts CHANGED
@@ -738,6 +738,98 @@ declare class StorageModule {
738
738
  getPublicFileUrl(fileId: string, projectId?: string): Promise<string>;
739
739
  }
740
740
 
741
+ /**
742
+ * Scalar values permitted inside a timeline filter. Matches the server
743
+ * filter allow-list (libs/shared-dtos/.../timeline-filter.dto.ts).
744
+ */
745
+ type TimelineScalar = string | number | boolean | null;
746
+ /**
747
+ * Single-field filter expression. Either implicit equality (a scalar) or
748
+ * an explicit operator object. Mongo-style allow-list — the server rejects
749
+ * anything outside this set.
750
+ */
751
+ type TimelineLeafFilter = TimelineScalar | {
752
+ $eq?: TimelineScalar;
753
+ $ne?: TimelineScalar;
754
+ $lt?: TimelineScalar;
755
+ $lte?: TimelineScalar;
756
+ $gt?: TimelineScalar;
757
+ $gte?: TimelineScalar;
758
+ $in?: TimelineScalar[];
759
+ $nin?: TimelineScalar[];
760
+ };
761
+ /**
762
+ * Filter document for a timeline query. Top-level fields apply implicit
763
+ * equality (or operator expression). $and supports nested filter
764
+ * composition; $or / $regex / etc. are intentionally NOT exposed and
765
+ * will be rejected server-side.
766
+ *
767
+ * Two named branches in the union so TypeScript doesn't treat `$and` as
768
+ * just another key under the field index signature — the array shape
769
+ * needs its own type or downstream usage breaks.
770
+ */
771
+ interface TimelineAndFilter {
772
+ $and: TimelineFilter[];
773
+ }
774
+ type TimelineFieldFilter = Record<string, TimelineLeafFilter>;
775
+ type TimelineFilter = TimelineAndFilter | TimelineFieldFilter;
776
+ interface TimelineOrderBy {
777
+ field: string;
778
+ direction: 'asc' | 'desc';
779
+ }
780
+ interface TimelineQueryOptions {
781
+ /** Collection name to read from. */
782
+ collection: string;
783
+ /** Partition value (e.g. roomId, documentId — collection-configured). */
784
+ partitionValue: string;
785
+ /** Optional filter constraints (allow-listed operators only). */
786
+ where?: TimelineFilter;
787
+ /**
788
+ * Sort order. Defaults to DESC by the collection's configured timeline
789
+ * field; PR A only supports DESC. Passing direction='asc' is reserved
790
+ * for a future phase and currently returns BAD_REQUEST.
791
+ */
792
+ orderBy?: TimelineOrderBy;
793
+ /** Page size (1..200). Default 50. */
794
+ limit?: number;
795
+ /**
796
+ * Opaque cursor from a previous response. The SDK does NOT decode this —
797
+ * pass the value of `nextCursor` from the last response verbatim.
798
+ */
799
+ cursor?: string;
800
+ }
801
+ interface TimelineSourceStats {
802
+ hot: number;
803
+ cold: number;
804
+ /** Number of cold archive segments scanned (only present when cold path ran). */
805
+ segmentsScanned?: number;
806
+ }
807
+ interface TimelineQueryResponse {
808
+ items: Record<string, unknown>[];
809
+ /** Opaque continuation token. `null` when the page is the last one. */
810
+ nextCursor: string | null;
811
+ sourceStats: TimelineSourceStats;
812
+ }
813
+
814
+ /**
815
+ * Low-level timeline SDK module. One method: query(opts) → page.
816
+ *
817
+ * The cursor is opaque from the SDK's perspective — it's stored as a
818
+ * string, never decoded, never inspected. Callers pass the value of
819
+ * `nextCursor` from the previous response verbatim into the next request.
820
+ *
821
+ * Errors thrown from query() extend SpacelrError so they integrate with
822
+ * the SDK's existing error hierarchy. Use one of the typed subclasses
823
+ * (CursorInvalidError, ForbiddenError, etc.) to catch specific cases or
824
+ * `catch (e instanceof TimelineError)` for any timeline-related failure.
825
+ */
826
+ declare class TimelineModule {
827
+ private readonly http;
828
+ constructor(http: HttpClient);
829
+ query(opts: TimelineQueryOptions): Promise<TimelineQueryResponse>;
830
+ private translateError;
831
+ }
832
+
741
833
  interface PopulateOption {
742
834
  field: string;
743
835
  collection: string;
@@ -1106,6 +1198,16 @@ declare class CollectionRef<T = Record<string, unknown>> {
1106
1198
  insertMany(documents: (Partial<T> & {
1107
1199
  _id?: string;
1108
1200
  })[]): Promise<InsertResult>;
1201
+ /**
1202
+ * Query the collection's **hot tier** (live MongoDB).
1203
+ *
1204
+ * **Cold-tier note:** if the collection has cold-tier archival enabled, aged
1205
+ * documents are moved to object storage and purged from the hot tier — they
1206
+ * will NOT appear in `find()` results (nor `findById`, `count`, `search`,
1207
+ * `paginate`, which are all hot-tier only). To read archived history, use
1208
+ * {@link TimelineModule.query} via `db.timeline.query(...)`, which transparently
1209
+ * merges hot and cold results for a partition.
1210
+ */
1109
1211
  find(filter?: Record<string, unknown>): QueryBuilder<T>;
1110
1212
  /**
1111
1213
  * Cursor-based scroll-back helper. Returns a `Paginator` whose `.next()`
@@ -1136,13 +1238,30 @@ declare class CollectionRef<T = Record<string, unknown>> {
1136
1238
  *
1137
1239
  * Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching
1138
1240
  * `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.
1241
+ *
1242
+ * **Cold-tier note:** searches the hot tier only; archived documents are not
1243
+ * included. See {@link CollectionRef.find} and `db.timeline.query(...)`.
1139
1244
  */
1140
1245
  search(opts: SearchOptions): Promise<SearchResult<T>>;
1246
+ /**
1247
+ * Fetch a single document by `_id` from the **hot tier**.
1248
+ *
1249
+ * **Cold-tier note:** returns null/throws for documents that have been
1250
+ * archived and purged from the hot tier. Archived history is reachable only
1251
+ * via `db.timeline.query(...)`. See {@link CollectionRef.find}.
1252
+ */
1141
1253
  findById(id: string, options?: FindByIdOptions): Promise<T & {
1142
1254
  _id: string;
1143
1255
  }>;
1144
1256
  update(id: string, update: Partial<T>): Promise<UpdateResult>;
1145
1257
  delete(id: string): Promise<DeleteResult>;
1258
+ /**
1259
+ * Count documents in the **hot tier** matching `filter`.
1260
+ *
1261
+ * **Cold-tier note:** counts hot-tier documents only — archived/purged
1262
+ * documents are not included, so this is not a total-history count. See
1263
+ * {@link CollectionRef.find} and `db.timeline.query(...)`.
1264
+ */
1146
1265
  count(filter?: Record<string, unknown>): Promise<number>;
1147
1266
  subscribe(handlers: SubscribeHandlers<T>): () => void;
1148
1267
  subscribeEvents(handlers: SubscribeEventsHandlers<T>): StreamSubscription;
@@ -1161,6 +1280,7 @@ declare class DatabaseModule {
1161
1280
  private http;
1162
1281
  private realtime;
1163
1282
  private projectId;
1283
+ readonly timeline: TimelineModule;
1164
1284
  constructor(http: HttpClient, projectId: string, realtime?: RealtimeClient);
1165
1285
  collection<T = Record<string, unknown>>(name: string): CollectionRef<T>;
1166
1286
  }
@@ -1269,12 +1389,127 @@ declare class FunctionsModule {
1269
1389
  invoke(projectId: string, functionId: string, options?: FunctionInvokeOptions): Promise<FunctionInvokeResult>;
1270
1390
  }
1271
1391
 
1392
+ type ScheduleStatus = 'scheduled' | 'fired' | 'failed' | 'cancelled';
1393
+ interface Schedule {
1394
+ id: string;
1395
+ projectId: string;
1396
+ functionId: string;
1397
+ payload: Record<string, unknown>;
1398
+ executeAt: string;
1399
+ idempotencyKey: string | null;
1400
+ status: ScheduleStatus;
1401
+ bullJobId: string | null;
1402
+ firedAt: string | null;
1403
+ executionId: string | null;
1404
+ attemptNumber: number;
1405
+ maxAttempts: number;
1406
+ lastError: string | null;
1407
+ createdAt: string;
1408
+ updatedAt: string;
1409
+ }
1410
+ interface ScheduleInvokeOptions {
1411
+ /** 24-char hex function id to invoke when the schedule fires. */
1412
+ functionId: string;
1413
+ /** Arbitrary JSON payload surfaced to the function as `event.payload`. */
1414
+ payload?: Record<string, unknown>;
1415
+ /**
1416
+ * When to fire. Accepts a `Date`, an ISO-8601 string, or epoch ms.
1417
+ * Server-side applies a 5-second past-skew tolerance and rejects anything
1418
+ * beyond `SCHEDULE_MAX_DELAY_DAYS` (default 90 days).
1419
+ */
1420
+ executeAt: Date | string | number;
1421
+ /**
1422
+ * Dedup key. Repeating the call with the same key in the same project for
1423
+ * the same function returns the existing schedule unchanged.
1424
+ */
1425
+ idempotencyKey?: string;
1426
+ /**
1427
+ * Maximum retry attempts after a failed execution (0–10, default 3).
1428
+ * Counts only retries — the initial fire is always attempted.
1429
+ */
1430
+ maxAttempts?: number;
1431
+ }
1432
+ interface ScheduleListOptions {
1433
+ functionId?: string;
1434
+ status?: ScheduleStatus;
1435
+ limit?: number;
1436
+ offset?: number;
1437
+ }
1438
+ declare class ScheduleModule {
1439
+ private readonly http;
1440
+ private readonly projectId;
1441
+ constructor(http: HttpClient, projectId: string);
1442
+ /**
1443
+ * Schedule a one-shot function invocation. Returns the existing handle
1444
+ * unchanged if `idempotencyKey` matches a prior invoke in this project
1445
+ * for this function.
1446
+ */
1447
+ invoke(options: ScheduleInvokeOptions): Promise<Schedule>;
1448
+ get(scheduleId: string): Promise<Schedule>;
1449
+ list(options?: ScheduleListOptions): Promise<{
1450
+ items: Schedule[];
1451
+ total: number;
1452
+ }>;
1453
+ cancel(scheduleId: string): Promise<Schedule>;
1454
+ private toIsoString;
1455
+ }
1456
+
1457
+ /**
1458
+ * Base class for all timeline-related errors thrown by the SDK. Extends
1459
+ * SpacelrError so it fits the SDK's existing `catch (e instanceof
1460
+ * SpacelrError)` pattern. Subclasses give callers fine-grained control
1461
+ * (e.g. catch only `CursorInvalidError` and restart pagination).
1462
+ */
1463
+ declare class TimelineError extends SpacelrError {
1464
+ constructor(message: string, opts?: {
1465
+ statusCode?: number;
1466
+ code?: string;
1467
+ });
1468
+ }
1469
+ declare class CursorInvalidError extends TimelineError {
1470
+ constructor(message: string, opts?: {
1471
+ statusCode?: number;
1472
+ code?: string;
1473
+ });
1474
+ }
1475
+ declare class ValidationError extends TimelineError {
1476
+ constructor(message: string, opts?: {
1477
+ statusCode?: number;
1478
+ code?: string;
1479
+ });
1480
+ }
1481
+ declare class ForbiddenError extends TimelineError {
1482
+ constructor(message: string, opts?: {
1483
+ statusCode?: number;
1484
+ code?: string;
1485
+ });
1486
+ }
1487
+ declare class NotFoundError extends TimelineError {
1488
+ constructor(message: string, opts?: {
1489
+ statusCode?: number;
1490
+ code?: string;
1491
+ });
1492
+ }
1493
+ declare class ServerConfigError extends TimelineError {
1494
+ constructor(message: string, opts?: {
1495
+ statusCode?: number;
1496
+ code?: string;
1497
+ });
1498
+ }
1499
+ declare class TimeoutError extends TimelineError {
1500
+ constructor(message: string, opts?: {
1501
+ statusCode?: number;
1502
+ code?: string;
1503
+ });
1504
+ }
1505
+
1272
1506
  interface SpacelrClient {
1273
1507
  readonly auth: AuthModule;
1274
1508
  readonly storage: StorageModule;
1275
1509
  readonly db: DatabaseModule;
1276
1510
  readonly notifications: NotificationsModule;
1277
1511
  readonly functions: FunctionsModule;
1512
+ readonly schedule: ScheduleModule;
1278
1513
  /**
1279
1514
  * Store auth tokens externally (e.g. obtained via the auth-components
1280
1515
  * library that bypasses the SDK). Makes the tokens available to HTTP +
@@ -1326,4 +1561,4 @@ interface SpacelrClient {
1326
1561
  }
1327
1562
  declare function createClient(config: SpacelrClientConfig): SpacelrClient;
1328
1563
 
1329
- export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type SearchOptions, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
1564
+ export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };