@trestleinc/replicate 0.1.0

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.
Files changed (39) hide show
  1. package/LICENSE +201 -0
  2. package/LICENSE.package +201 -0
  3. package/README.md +871 -0
  4. package/dist/client/collection.d.ts +94 -0
  5. package/dist/client/index.d.ts +18 -0
  6. package/dist/client/logger.d.ts +3 -0
  7. package/dist/client/storage.d.ts +143 -0
  8. package/dist/component/_generated/api.js +5 -0
  9. package/dist/component/_generated/server.js +9 -0
  10. package/dist/component/convex.config.d.ts +2 -0
  11. package/dist/component/convex.config.js +3 -0
  12. package/dist/component/public.d.ts +99 -0
  13. package/dist/component/public.js +135 -0
  14. package/dist/component/schema.d.ts +22 -0
  15. package/dist/component/schema.js +22 -0
  16. package/dist/index.js +375 -0
  17. package/dist/server/index.d.ts +17 -0
  18. package/dist/server/replication.d.ts +122 -0
  19. package/dist/server/schema.d.ts +73 -0
  20. package/dist/server/ssr.d.ts +79 -0
  21. package/dist/server.js +96 -0
  22. package/dist/ssr.js +19 -0
  23. package/package.json +108 -0
  24. package/src/client/collection.ts +550 -0
  25. package/src/client/index.ts +31 -0
  26. package/src/client/logger.ts +31 -0
  27. package/src/client/storage.ts +206 -0
  28. package/src/component/_generated/api.d.ts +95 -0
  29. package/src/component/_generated/api.js +23 -0
  30. package/src/component/_generated/dataModel.d.ts +60 -0
  31. package/src/component/_generated/server.d.ts +149 -0
  32. package/src/component/_generated/server.js +90 -0
  33. package/src/component/convex.config.ts +3 -0
  34. package/src/component/public.ts +212 -0
  35. package/src/component/schema.ts +16 -0
  36. package/src/server/index.ts +26 -0
  37. package/src/server/replication.ts +244 -0
  38. package/src/server/schema.ts +97 -0
  39. package/src/server/ssr.ts +106 -0
@@ -0,0 +1,94 @@
1
+ import type { ConvexClient } from 'convex/browser';
2
+ import type { FunctionReference } from 'convex/server';
3
+ import type { CollectionConfig, Collection } from '@tanstack/db';
4
+ /**
5
+ * Configuration for convexCollectionOptions (Step 1)
6
+ * All params go here - they'll be used to create the collection config
7
+ */
8
+ export interface ConvexCollectionOptionsConfig<T extends object> {
9
+ /** Function to extract unique key from items */
10
+ getKey: (item: T) => string | number;
11
+ /** Optional initial data to populate collection */
12
+ initialData?: ReadonlyArray<T>;
13
+ /** Convex client instance */
14
+ convexClient: ConvexClient;
15
+ /** Convex API functions for this collection */
16
+ api: {
17
+ stream: FunctionReference<'query'>;
18
+ insertDocument: FunctionReference<'mutation'>;
19
+ updateDocument: FunctionReference<'mutation'>;
20
+ deleteDocument: FunctionReference<'mutation'>;
21
+ };
22
+ /** Unique collection name */
23
+ collectionName: string;
24
+ }
25
+ /**
26
+ * ConvexCollection is now just a standard TanStack DB Collection!
27
+ * No custom wrapper, no special methods - uses built-in transaction system.
28
+ */
29
+ export type ConvexCollection<T extends object> = Collection<T>;
30
+ /**
31
+ * Step 1: Create TanStack DB CollectionConfig with REAL mutation handlers.
32
+ *
33
+ * This implements the CORRECT pattern:
34
+ * - Uses onInsert/onUpdate/onDelete handlers (not custom wrapper)
35
+ * - Yjs Y.Doc with 'update' event for delta encoding
36
+ * - Stores Y.Map instances (not plain objects) for field-level CRDT
37
+ * - Uses ydoc.transact() to batch changes into single 'update' event
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { createCollection } from '@tanstack/react-db'
42
+ * import { convexCollectionOptions } from '@trestleinc/convex-replicate-core'
43
+ *
44
+ * const rawCollection = createCollection(
45
+ * convexCollectionOptions<Task>({
46
+ * convexClient,
47
+ * api: api.tasks,
48
+ * collectionName: 'tasks',
49
+ * getKey: (task) => task.id,
50
+ * initialData,
51
+ * })
52
+ * )
53
+ * ```
54
+ */
55
+ export declare function convexCollectionOptions<T extends object>({ getKey, initialData, convexClient, api, collectionName, }: ConvexCollectionOptionsConfig<T>): CollectionConfig<T> & {
56
+ _convexClient: ConvexClient;
57
+ _collectionName: string;
58
+ };
59
+ /**
60
+ * Step 2: Wrap collection with offline support.
61
+ *
62
+ * This implements the CORRECT pattern:
63
+ * - Wraps collection ONCE with startOfflineExecutor
64
+ * - Returns raw collection (NO CUSTOM WRAPPER)
65
+ * - Uses beforeRetry filter for stale transactions
66
+ * - Connects to Convex connection state for retry triggers
67
+ *
68
+ * Config is automatically extracted from the rawCollection!
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * import { createCollection } from '@tanstack/react-db'
73
+ * import { convexCollectionOptions, createConvexCollection } from '@trestleinc/convex-replicate-core'
74
+ *
75
+ * // Step 1: Create raw collection with ALL config
76
+ * const rawCollection = createCollection(
77
+ * convexCollectionOptions<Task>({
78
+ * convexClient,
79
+ * api: api.tasks,
80
+ * collectionName: 'tasks',
81
+ * getKey: (task) => task.id,
82
+ * initialData,
83
+ * })
84
+ * )
85
+ *
86
+ * // Step 2: Wrap with offline support - params automatically extracted!
87
+ * const collection = createConvexCollection(rawCollection)
88
+ *
89
+ * // Use like a normal TanStack DB collection
90
+ * const tx = collection.insert({ id: '1', text: 'Buy milk', isCompleted: false })
91
+ * await tx.isPersisted.promise // Built-in promise (not custom awaitReplication)
92
+ * ```
93
+ */
94
+ export declare function createConvexCollection<T extends object>(rawCollection: Collection<T>): ConvexCollection<T>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Client-side utilities for browser/React code.
3
+ * Import this in your frontend components.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * // src/useTasks.ts
8
+ * import {
9
+ * convexCollectionOptions,
10
+ * createConvexCollection,
11
+ * type ConvexCollection,
12
+ * } from '@trestleinc/replicate/client';
13
+ * ```
14
+ */
15
+ export { ReplicateStorage } from './storage.js';
16
+ export { convexCollectionOptions, createConvexCollection, type ConvexCollection, type ConvexCollectionOptionsConfig, } from './collection.js';
17
+ export * as Y from 'yjs';
18
+ export { NonRetriableError } from '@tanstack/offline-transactions';
@@ -0,0 +1,3 @@
1
+ import { type Logger } from '@logtape/logtape';
2
+ export declare function configureLogger(enableLogging?: boolean): Promise<void>;
3
+ export declare function getLogger(category: string[]): Logger;
@@ -0,0 +1,143 @@
1
+ import type { Expand, FunctionReference, GenericDataModel, GenericMutationCtx, GenericQueryCtx } from 'convex/server';
2
+ import type { GenericId } from 'convex/values';
3
+ import { api } from '../component/_generated/api';
4
+ /**
5
+ * A client API for interacting with the Convex Replicate storage component.
6
+ *
7
+ * This class provides a type-safe, scoped interface for storing and retrieving
8
+ * CRDT document data from the replicate component. Each instance is scoped to
9
+ * a specific collection name, eliminating the need to pass collectionName to
10
+ * every method call.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { components } from "./_generated/api";
15
+ * import { ReplicateStorage } from "@trestleinc/convex-replicate-component";
16
+ *
17
+ * // Create a storage instance for the "tasks" collection
18
+ * const tasksStorage = new ReplicateStorage(components.replicate, "tasks");
19
+ *
20
+ * export const submitTask = mutation({
21
+ * handler: async (ctx, args) => {
22
+ * return await tasksStorage.submitDocument(
23
+ * ctx,
24
+ * args.id,
25
+ * args.document,
26
+ * args.version
27
+ * );
28
+ * }
29
+ * });
30
+ *
31
+ * export const getTasks = query({
32
+ * handler: async (ctx, args) => {
33
+ * return await tasksStorage.stream(ctx, args.checkpoint, args.limit);
34
+ * }
35
+ * });
36
+ * ```
37
+ *
38
+ * @template TDocument - The document type being stored (must have an id field)
39
+ */
40
+ export declare class ReplicateStorage<_TDocument extends {
41
+ id: string;
42
+ } = {
43
+ id: string;
44
+ }> {
45
+ private component;
46
+ private collectionName;
47
+ /**
48
+ * Create a new ReplicateStorage instance scoped to a specific collection.
49
+ *
50
+ * @param component - The replicate component from your generated API
51
+ * @param collectionName - The name of the collection to interact with
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const tasksStorage = new ReplicateStorage(
56
+ * components.replicate,
57
+ * "tasks"
58
+ * );
59
+ * ```
60
+ */
61
+ constructor(component: UseApi<typeof api>, collectionName: string);
62
+ /**
63
+ * Insert a new document into the replicate component storage.
64
+ *
65
+ * This stores the CRDT bytes in the component's internal storage table.
66
+ *
67
+ * @param ctx - Convex mutation context
68
+ * @param documentId - Unique identifier for the document
69
+ * @param crdtBytes - The CRDT binary data (from Automerge.save())
70
+ * @param version - Version number for conflict resolution
71
+ * @returns Success indicator
72
+ */
73
+ insertDocument(ctx: RunMutationCtx, documentId: string, crdtBytes: ArrayBuffer, version: number): Promise<{
74
+ success: boolean;
75
+ }>;
76
+ /**
77
+ * Update an existing document in the replicate component storage.
78
+ *
79
+ * @param ctx - Convex mutation context
80
+ * @param documentId - Unique identifier for the document
81
+ * @param crdtBytes - The CRDT binary data (from Automerge.save())
82
+ * @param version - Version number for conflict resolution
83
+ * @returns Success indicator
84
+ */
85
+ updateDocument(ctx: RunMutationCtx, documentId: string, crdtBytes: ArrayBuffer, version: number): Promise<{
86
+ success: boolean;
87
+ }>;
88
+ /**
89
+ * Delete a document from the replicate component storage.
90
+ * Appends deletion delta to event log.
91
+ *
92
+ * @param ctx - Convex mutation context
93
+ * @param documentId - Unique identifier for the document
94
+ * @param crdtBytes - Yjs deletion delta
95
+ * @param version - CRDT version number
96
+ * @returns Success indicator
97
+ */
98
+ deleteDocument(ctx: RunMutationCtx, documentId: string, crdtBytes: ArrayBuffer, version: number): Promise<{
99
+ success: boolean;
100
+ }>;
101
+ /**
102
+ * Stream CRDT changes for incremental replication.
103
+ *
104
+ * Retrieves CRDT bytes for documents that have been modified since the
105
+ * provided checkpoint, enabling incremental replication.
106
+ * Can be used for both polling (awaitReplication) and subscriptions (live updates).
107
+ *
108
+ * @param ctx - Convex query context
109
+ * @param checkpoint - Last known modification timestamp
110
+ * @param limit - Maximum number of changes to retrieve (default: 100)
111
+ * @returns Array of changes with updated checkpoint
112
+ */
113
+ stream(ctx: RunQueryCtx, checkpoint: {
114
+ lastModified: number;
115
+ }, limit?: number): Promise<{
116
+ changes: Array<{
117
+ documentId: string;
118
+ crdtBytes: ArrayBuffer;
119
+ version: number;
120
+ timestamp: number;
121
+ }>;
122
+ checkpoint: {
123
+ lastModified: number;
124
+ };
125
+ hasMore: boolean;
126
+ }>;
127
+ }
128
+ /**
129
+ * Re-export the component API for direct access if needed.
130
+ */
131
+ export { api };
132
+ type RunQueryCtx = {
133
+ runQuery: GenericQueryCtx<GenericDataModel>['runQuery'];
134
+ };
135
+ type RunMutationCtx = {
136
+ runMutation: GenericMutationCtx<GenericDataModel>['runMutation'];
137
+ };
138
+ export type OpaqueIds<T> = T extends GenericId<infer _T> ? string : T extends (infer U)[] ? OpaqueIds<U>[] : T extends object ? {
139
+ [K in keyof T]: OpaqueIds<T[K]>;
140
+ } : T;
141
+ export type UseApi<API> = Expand<{
142
+ [mod in keyof API]: API[mod] extends FunctionReference<infer FType, 'public', infer FArgs, infer FReturnType, infer FComponentPath> ? FunctionReference<FType, 'internal', OpaqueIds<FArgs>, OpaqueIds<FReturnType>, FComponentPath> : UseApi<API[mod]>;
143
+ }>;
@@ -0,0 +1,5 @@
1
+ import { anyApi, componentsGeneric } from "convex/server";
2
+ const api = anyApi;
3
+ const internal = anyApi;
4
+ const components = componentsGeneric();
5
+ export { api, components, internal };
@@ -0,0 +1,9 @@
1
+ import { actionGeneric, httpActionGeneric, internalActionGeneric, internalMutationGeneric, internalQueryGeneric, mutationGeneric, queryGeneric } from "convex/server";
2
+ const query = queryGeneric;
3
+ const internalQuery = internalQueryGeneric;
4
+ const mutation = mutationGeneric;
5
+ const internalMutation = internalMutationGeneric;
6
+ const action = actionGeneric;
7
+ const internalAction = internalActionGeneric;
8
+ const httpAction = httpActionGeneric;
9
+ export { action, httpAction, internalAction, internalMutation, internalQuery, mutation, query };
@@ -0,0 +1,2 @@
1
+ declare const _default: import("convex/server").ComponentDefinition<any>;
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ import { defineComponent } from "convex/server";
2
+ const convex_config = defineComponent('replicate');
3
+ export { convex_config as default };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Insert a new document with CRDT bytes (Yjs format).
3
+ * Appends delta to event log (event sourcing pattern).
4
+ *
5
+ * @param collectionName - Collection identifier
6
+ * @param documentId - Unique document identifier
7
+ * @param crdtBytes - ArrayBuffer containing Yjs CRDT bytes (delta)
8
+ * @param version - CRDT version number
9
+ */
10
+ export declare const insertDocument: import("convex/server").RegisteredMutation<"public", {
11
+ collectionName: string;
12
+ documentId: string;
13
+ crdtBytes: ArrayBuffer;
14
+ version: number;
15
+ }, Promise<{
16
+ success: boolean;
17
+ }>>;
18
+ /**
19
+ * Update an existing document with new CRDT bytes (Yjs format).
20
+ * Appends delta to event log (event sourcing pattern).
21
+ *
22
+ * @param collectionName - Collection identifier
23
+ * @param documentId - Unique document identifier
24
+ * @param crdtBytes - ArrayBuffer containing Yjs CRDT bytes (delta)
25
+ * @param version - CRDT version number
26
+ */
27
+ export declare const updateDocument: import("convex/server").RegisteredMutation<"public", {
28
+ collectionName: string;
29
+ documentId: string;
30
+ crdtBytes: ArrayBuffer;
31
+ version: number;
32
+ }, Promise<{
33
+ success: boolean;
34
+ }>>;
35
+ /**
36
+ * Delete a document from CRDT storage.
37
+ * Appends deletion delta to event log (preserves history).
38
+ *
39
+ * @param collectionName - Collection identifier
40
+ * @param documentId - Unique document identifier
41
+ * @param crdtBytes - ArrayBuffer containing Yjs deletion delta
42
+ * @param version - CRDT version number
43
+ */
44
+ export declare const deleteDocument: import("convex/server").RegisteredMutation<"public", {
45
+ collectionName: string;
46
+ documentId: string;
47
+ crdtBytes: ArrayBuffer;
48
+ version: number;
49
+ }, Promise<{
50
+ success: boolean;
51
+ }>>;
52
+ /**
53
+ * Get complete event history for a document.
54
+ * Returns all CRDT deltas in chronological order.
55
+ *
56
+ * Used for:
57
+ * - Future recovery features (client-side)
58
+ * - Audit trails
59
+ * - Debugging
60
+ *
61
+ * @param collectionName - Collection identifier
62
+ * @param documentId - Unique document identifier
63
+ */
64
+ export declare const getDocumentHistory: import("convex/server").RegisteredQuery<"public", {
65
+ collectionName: string;
66
+ documentId: string;
67
+ }, Promise<{
68
+ crdtBytes: ArrayBuffer;
69
+ version: number;
70
+ timestamp: number;
71
+ operationType: string;
72
+ }[]>>;
73
+ /**
74
+ * Stream CRDT changes for incremental replication.
75
+ * Returns Yjs CRDT bytes for documents modified since the checkpoint.
76
+ * Can be used for both polling (awaitReplication) and subscriptions (live updates).
77
+ *
78
+ * @param collectionName - Collection identifier
79
+ * @param checkpoint - Last replication checkpoint
80
+ * @param limit - Maximum number of changes to return (default: 100)
81
+ */
82
+ export declare const stream: import("convex/server").RegisteredQuery<"public", {
83
+ limit?: number | undefined;
84
+ collectionName: string;
85
+ checkpoint: {
86
+ lastModified: number;
87
+ };
88
+ }, Promise<{
89
+ changes: {
90
+ documentId: string;
91
+ crdtBytes: ArrayBuffer;
92
+ version: number;
93
+ timestamp: number;
94
+ }[];
95
+ checkpoint: {
96
+ lastModified: number;
97
+ };
98
+ hasMore: boolean;
99
+ }>>;
@@ -0,0 +1,135 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server.js";
3
+ const insertDocument = mutation({
4
+ args: {
5
+ collectionName: v.string(),
6
+ documentId: v.string(),
7
+ crdtBytes: v.bytes(),
8
+ version: v.number()
9
+ },
10
+ returns: v.object({
11
+ success: v.boolean()
12
+ }),
13
+ handler: async (ctx, args)=>{
14
+ await ctx.db.insert('documents', {
15
+ collectionName: args.collectionName,
16
+ documentId: args.documentId,
17
+ crdtBytes: args.crdtBytes,
18
+ version: args.version,
19
+ timestamp: Date.now(),
20
+ operationType: 'insert'
21
+ });
22
+ return {
23
+ success: true
24
+ };
25
+ }
26
+ });
27
+ const updateDocument = mutation({
28
+ args: {
29
+ collectionName: v.string(),
30
+ documentId: v.string(),
31
+ crdtBytes: v.bytes(),
32
+ version: v.number()
33
+ },
34
+ returns: v.object({
35
+ success: v.boolean()
36
+ }),
37
+ handler: async (ctx, args)=>{
38
+ await ctx.db.insert('documents', {
39
+ collectionName: args.collectionName,
40
+ documentId: args.documentId,
41
+ crdtBytes: args.crdtBytes,
42
+ version: args.version,
43
+ timestamp: Date.now(),
44
+ operationType: 'update'
45
+ });
46
+ return {
47
+ success: true
48
+ };
49
+ }
50
+ });
51
+ const deleteDocument = mutation({
52
+ args: {
53
+ collectionName: v.string(),
54
+ documentId: v.string(),
55
+ crdtBytes: v.bytes(),
56
+ version: v.number()
57
+ },
58
+ returns: v.object({
59
+ success: v.boolean()
60
+ }),
61
+ handler: async (ctx, args)=>{
62
+ await ctx.db.insert('documents', {
63
+ collectionName: args.collectionName,
64
+ documentId: args.documentId,
65
+ crdtBytes: args.crdtBytes,
66
+ version: args.version,
67
+ timestamp: Date.now(),
68
+ operationType: 'delete'
69
+ });
70
+ return {
71
+ success: true
72
+ };
73
+ }
74
+ });
75
+ const getDocumentHistory = query({
76
+ args: {
77
+ collectionName: v.string(),
78
+ documentId: v.string()
79
+ },
80
+ returns: v.array(v.object({
81
+ crdtBytes: v.bytes(),
82
+ version: v.number(),
83
+ timestamp: v.number(),
84
+ operationType: v.string()
85
+ })),
86
+ handler: async (ctx, args)=>{
87
+ const deltas = await ctx.db.query('documents').withIndex('by_collection_document_version', (q)=>q.eq('collectionName', args.collectionName).eq('documentId', args.documentId)).order('asc').collect();
88
+ return deltas.map((d)=>({
89
+ crdtBytes: d.crdtBytes,
90
+ version: d.version,
91
+ timestamp: d.timestamp,
92
+ operationType: d.operationType
93
+ }));
94
+ }
95
+ });
96
+ const stream = query({
97
+ args: {
98
+ collectionName: v.string(),
99
+ checkpoint: v.object({
100
+ lastModified: v.number()
101
+ }),
102
+ limit: v.optional(v.number())
103
+ },
104
+ returns: v.object({
105
+ changes: v.array(v.object({
106
+ documentId: v.string(),
107
+ crdtBytes: v.bytes(),
108
+ version: v.number(),
109
+ timestamp: v.number()
110
+ })),
111
+ checkpoint: v.object({
112
+ lastModified: v.number()
113
+ }),
114
+ hasMore: v.boolean()
115
+ }),
116
+ handler: async (ctx, args)=>{
117
+ const limit = args.limit ?? 100;
118
+ const documents = await ctx.db.query('documents').withIndex('by_timestamp', (q)=>q.eq('collectionName', args.collectionName).gt('timestamp', args.checkpoint.lastModified)).order('asc').take(limit);
119
+ const changes = documents.map((doc)=>({
120
+ documentId: doc.documentId,
121
+ crdtBytes: doc.crdtBytes,
122
+ version: doc.version,
123
+ timestamp: doc.timestamp
124
+ }));
125
+ const newCheckpoint = {
126
+ lastModified: documents.length > 0 ? documents[documents.length - 1]?.timestamp ?? args.checkpoint.lastModified : args.checkpoint.lastModified
127
+ };
128
+ return {
129
+ changes,
130
+ checkpoint: newCheckpoint,
131
+ hasMore: documents.length === limit
132
+ };
133
+ }
134
+ });
135
+ export { deleteDocument, getDocumentHistory, insertDocument, stream, updateDocument };
@@ -0,0 +1,22 @@
1
+ declare const _default: import("convex/server").SchemaDefinition<{
2
+ documents: import("convex/server").TableDefinition<import("convex/values").VObject<{
3
+ collectionName: string;
4
+ documentId: string;
5
+ crdtBytes: ArrayBuffer;
6
+ version: number;
7
+ timestamp: number;
8
+ operationType: string;
9
+ }, {
10
+ collectionName: import("convex/values").VString<string, "required">;
11
+ documentId: import("convex/values").VString<string, "required">;
12
+ crdtBytes: import("convex/values").VBytes<ArrayBuffer, "required">;
13
+ version: import("convex/values").VFloat64<number, "required">;
14
+ timestamp: import("convex/values").VFloat64<number, "required">;
15
+ operationType: import("convex/values").VString<string, "required">;
16
+ }, "required", "collectionName" | "documentId" | "crdtBytes" | "version" | "timestamp" | "operationType">, {
17
+ by_collection: ["collectionName", "_creationTime"];
18
+ by_collection_document_version: ["collectionName", "documentId", "version", "_creationTime"];
19
+ by_timestamp: ["collectionName", "timestamp", "_creationTime"];
20
+ }, {}, {}>;
21
+ }, true>;
22
+ export default _default;
@@ -0,0 +1,22 @@
1
+ import { defineSchema, defineTable } from "convex/server";
2
+ import { v } from "convex/values";
3
+ const schema = defineSchema({
4
+ documents: defineTable({
5
+ collectionName: v.string(),
6
+ documentId: v.string(),
7
+ crdtBytes: v.bytes(),
8
+ version: v.number(),
9
+ timestamp: v.number(),
10
+ operationType: v.string()
11
+ }).index('by_collection', [
12
+ 'collectionName'
13
+ ]).index('by_collection_document_version', [
14
+ 'collectionName',
15
+ 'documentId',
16
+ 'version'
17
+ ]).index('by_timestamp', [
18
+ 'collectionName',
19
+ 'timestamp'
20
+ ])
21
+ });
22
+ export { schema as default };