@gamerdexapp/gamerdex-sync 0.1.0 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-10-17
11
+
12
+ ### Added
13
+
14
+ - `GoogleDriveProvider` keeps the files in the Google Drive of the player, in a visible `Gamerdex`
15
+ folder, with the narrow `drive.file` scope. Drive cannot refuse a write made from an old version,
16
+ so the provider checks the head revision before the upload and the revision list after it. When
17
+ another device wrote in between, its content is put back and the sync merges both sides.
18
+ - `googleAuthorizeUrl()` and `GOOGLE_DRIVE_SCOPE` for the Google sign in page.
19
+ - `ProviderError.detail` also reads the message of a failed Google request.
20
+
21
+ ## [0.1.1] - 2026-10-17
22
+
23
+ ### Added
24
+
25
+ - `ProviderError.detail` carries the message that GitHub sends with a failed request, such as
26
+ "Resource not accessible by integration", so an app can log the reason of a failure.
27
+
28
+ ### Changed
29
+
30
+ - A sync that changes nothing no longer writes the meta file, so the repository gets no empty
31
+ commits. The meta file is still written when data is uploaded, when a device syncs for the first
32
+ time, and when the app changes the revisions.
33
+
10
34
  ## [0.1.0] - 2026-10-17
11
35
 
12
36
  First release.
package/dist/index.d.mts CHANGED
@@ -188,7 +188,11 @@ declare class ProviderError extends Error {
188
188
  readonly kind: ProviderErrorKind;
189
189
  readonly status?: number | undefined;
190
190
  readonly retryAfterSeconds?: number | undefined;
191
- constructor(kind: ProviderErrorKind, message: string, status?: number | undefined, retryAfterSeconds?: number | undefined);
191
+ /** The message that the provider sent with the failed request, when it sent one. */
192
+ readonly detail?: string | undefined;
193
+ constructor(kind: ProviderErrorKind, message: string, status?: number | undefined, retryAfterSeconds?: number | undefined,
194
+ /** The message that the provider sent with the failed request, when it sent one. */
195
+ detail?: string | undefined);
192
196
  }
193
197
 
194
198
  interface GitHubOptions {
@@ -389,4 +393,53 @@ interface InstalledRepository {
389
393
  */
390
394
  declare const listInstalledRepositories: (token: string, send?: typeof fetch) => Promise<InstalledRepository[]>;
391
395
 
392
- export { type CallbackResult, type Conflict, type ConflictKind, DATA_FILE, type DeviceRecord, type FirstSyncChoice, GITHUB_APP, type GitHubOptions, GitHubProvider, type InstalledRepository, META_FILE, META_FORMAT, MemoryProvider, type MergeInput, type MergeResult, type ParsedMeta, type Path, type PathStep, ProviderError, type ProviderErrorKind, ROOT_FOLDER, RemoteDataError, type RemoteFile, type RepositoryCheck, type Schema, type Side, type StoredTokens, SyncBusyError, type SyncMeta, type SyncOptions, type SyncProvider, type SyncResult, type SyncState, type TokenResponse, type TokenSourceOptions, VersionConflictError, authorizeUrl, createTokenSource, dataPath, decodeBase64, emptyMeta, encodeBase64, equal, installUrl, isAppId, isObject, listInstalledRepositories, merge, metaPath, newState, parseMeta, parseTokenResponse, readCallback, resolveConflict, restoreCloudValues, sync, toStoredTokens, withChiefRemoved, withChiefRevision, withDevice, withSettingsRevision };
396
+ interface GoogleDriveOptions {
397
+ /** Returns the token. It may refresh the token first, so it is called for every request. */
398
+ getToken: () => Promise<string>;
399
+ fetch?: typeof fetch;
400
+ }
401
+ /**
402
+ * Keeps the files of an app in the Google Drive of the player, in a visible `Gamerdex` folder. It
403
+ * needs only the `drive.file` scope, which sees the files that this app created.
404
+ *
405
+ * Drive cannot refuse a write made from an old version. Writes are checked in two steps instead:
406
+ * the head revision must equal the expected version before the upload, and the revision list must
407
+ * show the new revision directly on top of the expected one after it. When another device wrote in
408
+ * between, its content is written back as the head and `VersionConflictError` is thrown, so the
409
+ * next sync merges both sides.
410
+ */
411
+ declare class GoogleDriveProvider implements SyncProvider {
412
+ private readonly options;
413
+ private readonly send;
414
+ private readonly folders;
415
+ constructor(options: GoogleDriveOptions);
416
+ read(path: string): Promise<RemoteFile | null>;
417
+ write(path: string, content: string, expectedVersion: string | null): Promise<{
418
+ version: string;
419
+ }>;
420
+ remove(path: string, expectedVersion: string): Promise<void>;
421
+ private create;
422
+ private findFile;
423
+ private locate;
424
+ private folderId;
425
+ private children;
426
+ private headOf;
427
+ private revisionContent;
428
+ private revisionBefore;
429
+ private upload;
430
+ private createFile;
431
+ private deleteItem;
432
+ private stringField;
433
+ private request;
434
+ private failure;
435
+ }
436
+
437
+ /** The only Drive scope that sync asks for. It sees the files that the app created itself. */
438
+ declare const GOOGLE_DRIVE_SCOPE = "https://www.googleapis.com/auth/drive.file";
439
+ /**
440
+ * The address of the Google sign in page. `access_type=offline` and `prompt=consent` make Google
441
+ * send a refresh token, which it otherwise sends only the first time the player agrees.
442
+ */
443
+ declare const googleAuthorizeUrl: (clientId: string, redirectUri: string, state: string) => string;
444
+
445
+ export { type CallbackResult, type Conflict, type ConflictKind, DATA_FILE, type DeviceRecord, type FirstSyncChoice, GITHUB_APP, GOOGLE_DRIVE_SCOPE, type GitHubOptions, GitHubProvider, type GoogleDriveOptions, GoogleDriveProvider, type InstalledRepository, META_FILE, META_FORMAT, MemoryProvider, type MergeInput, type MergeResult, type ParsedMeta, type Path, type PathStep, ProviderError, type ProviderErrorKind, ROOT_FOLDER, RemoteDataError, type RemoteFile, type RepositoryCheck, type Schema, type Side, type StoredTokens, SyncBusyError, type SyncMeta, type SyncOptions, type SyncProvider, type SyncResult, type SyncState, type TokenResponse, type TokenSourceOptions, VersionConflictError, authorizeUrl, createTokenSource, dataPath, decodeBase64, emptyMeta, encodeBase64, equal, googleAuthorizeUrl, installUrl, isAppId, isObject, listInstalledRepositories, merge, metaPath, newState, parseMeta, parseTokenResponse, readCallback, resolveConflict, restoreCloudValues, sync, toStoredTokens, withChiefRemoved, withChiefRevision, withDevice, withSettingsRevision };
package/dist/index.d.ts CHANGED
@@ -188,7 +188,11 @@ declare class ProviderError extends Error {
188
188
  readonly kind: ProviderErrorKind;
189
189
  readonly status?: number | undefined;
190
190
  readonly retryAfterSeconds?: number | undefined;
191
- constructor(kind: ProviderErrorKind, message: string, status?: number | undefined, retryAfterSeconds?: number | undefined);
191
+ /** The message that the provider sent with the failed request, when it sent one. */
192
+ readonly detail?: string | undefined;
193
+ constructor(kind: ProviderErrorKind, message: string, status?: number | undefined, retryAfterSeconds?: number | undefined,
194
+ /** The message that the provider sent with the failed request, when it sent one. */
195
+ detail?: string | undefined);
192
196
  }
193
197
 
194
198
  interface GitHubOptions {
@@ -389,4 +393,53 @@ interface InstalledRepository {
389
393
  */
390
394
  declare const listInstalledRepositories: (token: string, send?: typeof fetch) => Promise<InstalledRepository[]>;
391
395
 
392
- export { type CallbackResult, type Conflict, type ConflictKind, DATA_FILE, type DeviceRecord, type FirstSyncChoice, GITHUB_APP, type GitHubOptions, GitHubProvider, type InstalledRepository, META_FILE, META_FORMAT, MemoryProvider, type MergeInput, type MergeResult, type ParsedMeta, type Path, type PathStep, ProviderError, type ProviderErrorKind, ROOT_FOLDER, RemoteDataError, type RemoteFile, type RepositoryCheck, type Schema, type Side, type StoredTokens, SyncBusyError, type SyncMeta, type SyncOptions, type SyncProvider, type SyncResult, type SyncState, type TokenResponse, type TokenSourceOptions, VersionConflictError, authorizeUrl, createTokenSource, dataPath, decodeBase64, emptyMeta, encodeBase64, equal, installUrl, isAppId, isObject, listInstalledRepositories, merge, metaPath, newState, parseMeta, parseTokenResponse, readCallback, resolveConflict, restoreCloudValues, sync, toStoredTokens, withChiefRemoved, withChiefRevision, withDevice, withSettingsRevision };
396
+ interface GoogleDriveOptions {
397
+ /** Returns the token. It may refresh the token first, so it is called for every request. */
398
+ getToken: () => Promise<string>;
399
+ fetch?: typeof fetch;
400
+ }
401
+ /**
402
+ * Keeps the files of an app in the Google Drive of the player, in a visible `Gamerdex` folder. It
403
+ * needs only the `drive.file` scope, which sees the files that this app created.
404
+ *
405
+ * Drive cannot refuse a write made from an old version. Writes are checked in two steps instead:
406
+ * the head revision must equal the expected version before the upload, and the revision list must
407
+ * show the new revision directly on top of the expected one after it. When another device wrote in
408
+ * between, its content is written back as the head and `VersionConflictError` is thrown, so the
409
+ * next sync merges both sides.
410
+ */
411
+ declare class GoogleDriveProvider implements SyncProvider {
412
+ private readonly options;
413
+ private readonly send;
414
+ private readonly folders;
415
+ constructor(options: GoogleDriveOptions);
416
+ read(path: string): Promise<RemoteFile | null>;
417
+ write(path: string, content: string, expectedVersion: string | null): Promise<{
418
+ version: string;
419
+ }>;
420
+ remove(path: string, expectedVersion: string): Promise<void>;
421
+ private create;
422
+ private findFile;
423
+ private locate;
424
+ private folderId;
425
+ private children;
426
+ private headOf;
427
+ private revisionContent;
428
+ private revisionBefore;
429
+ private upload;
430
+ private createFile;
431
+ private deleteItem;
432
+ private stringField;
433
+ private request;
434
+ private failure;
435
+ }
436
+
437
+ /** The only Drive scope that sync asks for. It sees the files that the app created itself. */
438
+ declare const GOOGLE_DRIVE_SCOPE = "https://www.googleapis.com/auth/drive.file";
439
+ /**
440
+ * The address of the Google sign in page. `access_type=offline` and `prompt=consent` make Google
441
+ * send a refresh token, which it otherwise sends only the first time the player agrees.
442
+ */
443
+ declare const googleAuthorizeUrl: (clientId: string, redirectUri: string, state: string) => string;
444
+
445
+ export { type CallbackResult, type Conflict, type ConflictKind, DATA_FILE, type DeviceRecord, type FirstSyncChoice, GITHUB_APP, GOOGLE_DRIVE_SCOPE, type GitHubOptions, GitHubProvider, type GoogleDriveOptions, GoogleDriveProvider, type InstalledRepository, META_FILE, META_FORMAT, MemoryProvider, type MergeInput, type MergeResult, type ParsedMeta, type Path, type PathStep, ProviderError, type ProviderErrorKind, ROOT_FOLDER, RemoteDataError, type RemoteFile, type RepositoryCheck, type Schema, type Side, type StoredTokens, SyncBusyError, type SyncMeta, type SyncOptions, type SyncProvider, type SyncResult, type SyncState, type TokenResponse, type TokenSourceOptions, VersionConflictError, authorizeUrl, createTokenSource, dataPath, decodeBase64, emptyMeta, encodeBase64, equal, googleAuthorizeUrl, installUrl, isAppId, isObject, listInstalledRepositories, merge, metaPath, newState, parseMeta, parseTokenResponse, readCallback, resolveConflict, restoreCloudValues, sync, toStoredTokens, withChiefRemoved, withChiefRevision, withDevice, withSettingsRevision };