-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add standard offline sync runtime #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
6d2ab78
feat: add standard offline sync runtime
rdlabo ee00093
fix: harden offline session boundaries
rdlabo a67cff1
fix: recover interrupted offline sessions
rdlabo dab633e
fix: await outbox state transitions
rdlabo 390522c
feat: standardize offline replica sync
rdlabo cabb5a4
fix: harden offline replica recovery
rdlabo be1f7ff
fix: serialize user scoped offline commands
rdlabo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", | ||
| "lib": { | ||
| "entryFile": "src/public-api.ts" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { InjectionToken } from '@angular/core'; | ||
| import type { OfflineCommand, OfflineScope } from './offline-repository'; | ||
|
|
||
| /** Server acknowledgement used to reconcile one optimistic local mutation. */ | ||
| export interface OfflineCommandResult { | ||
| /** AUTO_INCREMENT id returned by a successful create. */ | ||
| serverId?: number; | ||
| serverRevision?: string | number; | ||
| /** Full server-confirmed domain values after applying the mutation. */ | ||
| confirmedValues?: unknown; | ||
| /** Removes the local replica row after a confirmed server delete. */ | ||
| removeReplica?: boolean; | ||
| response?: unknown; | ||
| } | ||
|
|
||
| /** Target ids resolved from the local replica immediately before transport. */ | ||
| export interface OfflineCommandTarget { | ||
| localId: string; | ||
| serverId: number | null; | ||
| } | ||
|
|
||
| /** 不透明なoperationを製品APIへ送信し、local replicaへ投影するadapter。 */ | ||
| /** Product adapter that sends commands and projects acknowledgements into entities. */ | ||
| export interface OfflineCommandExecutor { | ||
| /** Sends the command using `command.commandId` as its durable server-side idempotency key. */ | ||
| execute(command: OfflineCommand, target: OfflineCommandTarget): Promise<OfflineCommandResult>; | ||
| withServerRevision(command: OfflineCommand, revision: string | number): OfflineCommand; | ||
| } | ||
|
|
||
| /** DI token for the product-specific command transport adapter. */ | ||
| export const OFFLINE_COMMAND_EXECUTOR = new InjectionToken<OfflineCommandExecutor>('OFFLINE_COMMAND_EXECUTOR'); | ||
|
|
||
| /** Authenticated user and group scopes currently eligible for synchronization. */ | ||
| export interface OfflineSyncSession { | ||
| userId: number; | ||
| scopes: OfflineScope[]; | ||
| } | ||
|
|
||
| /** Product adapter that exposes the currently authenticated synchronization session. */ | ||
| export interface OfflineSyncContext { | ||
| getSession(): Promise<OfflineSyncSession | null>; | ||
| } | ||
|
|
||
| /** DI token for authenticated synchronization context. */ | ||
| export const OFFLINE_SYNC_CONTEXT = new InjectionToken<OfflineSyncContext>('OFFLINE_SYNC_CONTEXT'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { InjectionToken } from '@angular/core'; | ||
| import type { OfflineCommand } from './offline-repository'; | ||
|
|
||
| /** Optional product hooks for entity projection and command cleanup. */ | ||
| export interface OfflineCommandHooks { | ||
| entityType(command: Pick<OfflineCommand, 'operation' | 'aggregateType'>): string; | ||
| onCommandRemoved?(command: OfflineCommand): Promise<void>; | ||
| } | ||
|
|
||
| export const DEFAULT_OFFLINE_COMMAND_HOOKS: OfflineCommandHooks = { | ||
| entityType: (command) => command.aggregateType, | ||
| }; | ||
|
|
||
| /** DI token for optional product-specific synchronization hooks. */ | ||
| export const OFFLINE_COMMAND_HOOKS = new InjectionToken<OfflineCommandHooks>('OFFLINE_COMMAND_HOOKS', { | ||
| providedIn: 'root', | ||
| factory: () => DEFAULT_OFFLINE_COMMAND_HOOKS, | ||
| }); |
53 changes: 53 additions & 0 deletions
53
projects/kit/offline/src/lib/offline-coordinator.service.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { inject, Injectable } from '@angular/core'; | ||
| import { OfflineNetworkService } from './offline-network.service'; | ||
| import { OFFLINE_REPOSITORY } from './offline-repository'; | ||
| import { OfflineSessionService } from './offline-session.service'; | ||
| import { OfflineSyncService } from './offline-sync.service'; | ||
|
|
||
| /** User choice when logout encounters unconfirmed local mutations. */ | ||
| export type OfflineLogoutAction = 'sync' | 'discard' | 'cancel'; | ||
|
|
||
| /** Coordinates local persistence, session boundaries, network state, and outbox synchronization. */ | ||
| @Injectable({ providedIn: 'root' }) | ||
| export class OfflineCoordinatorService { | ||
| readonly #repository = inject(OFFLINE_REPOSITORY); | ||
| readonly #network = inject(OfflineNetworkService); | ||
| readonly #sync = inject(OfflineSyncService); | ||
| readonly #session = inject(OfflineSessionService); | ||
|
|
||
| readonly networkState = this.#network.state; | ||
| readonly syncState = this.#sync.syncState; | ||
| readonly pendingCount = this.#sync.pendingCount; | ||
| readonly conflicts = this.#sync.conflicts; | ||
|
|
||
| async initialize(): Promise<void> { | ||
| await Promise.all([this.#repository.initialize(), this.#network.initialize()]); | ||
| await this.#session.initialize(); | ||
| await this.#sync.initialize(); | ||
| } | ||
|
|
||
| async activateSession(userId: number, scopeIds: readonly number[], authSubject: string | null): Promise<void> { | ||
| await this.#sync.resetSession(); | ||
| await this.#session.activateSession(userId, scopeIds, authSubject); | ||
| await this.#sync.refreshSession(); | ||
| } | ||
|
|
||
| async clearActiveSession(): Promise<void> { | ||
| await this.#sync.resetSession(); | ||
| await this.#session.clearActiveSession(); | ||
| } | ||
|
|
||
| async prepareLogout(action: OfflineLogoutAction): Promise<boolean> { | ||
| if (action === 'cancel') return false; | ||
| if (action === 'discard') { | ||
| await this.#sync.discardAllPending(); | ||
| return true; | ||
| } | ||
| await this.#sync.flush(); | ||
| return this.#sync.pendingCount() === 0; | ||
| } | ||
|
|
||
| flush(): Promise<void> { | ||
| return this.#sync.flush(); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { InjectionToken } from '@angular/core'; | ||
| import type { OfflineReplicaSchemaBundle } from './offline-replica-schema'; | ||
|
|
||
| /** Product-independent native offline persistence settings. */ | ||
| export interface OfflineKitOptions { | ||
| /** Encrypted SQLite database name used on iOS and Android. */ | ||
| databaseName: string; | ||
| /** Resolves the native database key from secure device storage. Required on iOS and Android. */ | ||
| encryptionKey?: () => Promise<string>; | ||
| /** Versioned product replica schema applied to native SQLite during initialization. */ | ||
| replicaSchema: OfflineReplicaSchemaBundle; | ||
| } | ||
|
|
||
| /** DI token for product-independent offline persistence settings. */ | ||
| export const OFFLINE_KIT_OPTIONS = new InjectionToken<OfflineKitOptions>('OFFLINE_KIT_OPTIONS'); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.