@aerogel/plugin-local-first 0.0.0 → 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 (35) hide show
  1. package/dist/aerogel-plugin-local-first.d.ts +48 -17
  2. package/dist/aerogel-plugin-local-first.js +1219 -1232
  3. package/dist/aerogel-plugin-local-first.js.map +1 -1
  4. package/package.json +5 -5
  5. package/src/components/Account.vue +9 -0
  6. package/src/components/AccountLoggedIn.vue +85 -0
  7. package/src/components/AccountLoggedOut.vue +26 -0
  8. package/src/components/AccountLoginModal.vue +36 -0
  9. package/src/components/AccountModal.vue +206 -0
  10. package/src/components/index.ts +5 -3
  11. package/src/components/settings/PurgeData.vue +43 -0
  12. package/src/components/settings/index.ts +10 -0
  13. package/src/index.css +1 -0
  14. package/src/index.ts +5 -1
  15. package/src/jobs/Migrate.test.ts +1 -1
  16. package/src/jobs/Migrate.ts +8 -1
  17. package/src/jobs/Sync.test.ts +313 -35
  18. package/src/jobs/Sync.ts +183 -77
  19. package/src/jobs/mixins/LoadsChildren.ts +48 -31
  20. package/src/jobs/mixins/LoadsTypeIndex.ts +7 -5
  21. package/src/jobs/mixins/TracksLocalModels.ts +4 -4
  22. package/src/lib/mirroring.ts +45 -21
  23. package/src/services/Cloud.ts +84 -25
  24. package/src/services/DocumentsCache.ts +6 -2
  25. package/src/testing/cloud.ts +1 -1
  26. package/src/testing/setup.ts +3 -2
  27. package/src/testing/stubs/Person.schema.ts +9 -0
  28. package/src/testing/stubs/Person.ts +18 -0
  29. package/src/testing/stubs/Post.schema.ts +17 -0
  30. package/src/testing/stubs/Post.ts +18 -0
  31. package/dist/InruptAuthenticator.lazy-BvpQLGoj-BvpQLGoj.js +0 -9
  32. package/dist/InruptAuthenticator.lazy-BvpQLGoj-BvpQLGoj.js.map +0 -1
  33. package/dist/LegacyAuthenticator.lazy-ChFFqP14-ChFFqP14.js +0 -5
  34. package/dist/LegacyAuthenticator.lazy-ChFFqP14-ChFFqP14.js.map +0 -1
  35. package/src/components/AGCloudStatus.vue +0 -31
@@ -5,18 +5,18 @@ import {
5
5
  requireUrlParentDirectory,
6
6
  required,
7
7
  tap,
8
- toString,
9
8
  urlResolveDirectory,
10
9
  urlRoute,
11
10
  } from '@noeldemartin/utils';
12
11
  import { App } from '@aerogel/core';
13
12
  import { getTrackedModels, trackModelsCollection as trackSoukaiModelsCollection } from '@aerogel/plugin-soukai';
14
13
  import { Solid } from '@aerogel/plugin-solid';
15
- import { PropertyOperation, SolidContainer, SolidContainsRelation, Tombstone } from 'soukai-solid';
14
+ import { PropertyOperation, SolidContainer, SolidContainsRelation, Tombstone, isContainerClass } from 'soukai-solid';
16
15
  import type { ObjectsMap } from '@noeldemartin/utils';
17
16
  import type { Attributes } from 'soukai';
18
- import type { SolidModel, SolidModelConstructor, SolidSchemaDefinition } from 'soukai-solid';
17
+ import type { Operation, SolidModel, SolidModelConstructor, SolidSchemaDefinition } from 'soukai-solid';
19
18
 
19
+ import DocumentsCache from '@aerogel/plugin-local-first/services/DocumentsCache';
20
20
  import SyncQueue from '@aerogel/plugin-local-first/lib/SyncQueue';
21
21
  import { getContainerRelations, getRelatedAppModels } from '@aerogel/plugin-local-first/lib/inference';
22
22
 
@@ -88,6 +88,10 @@ export async function trackModelsCollection(modelClass: SolidModelConstructor, c
88
88
  [modelClass.modelName]: (Cloud.modelCollections[modelClass.modelName] ?? []).concat(collection),
89
89
  });
90
90
 
91
+ if (!Cloud.modelCollections[modelClass.modelName]?.includes(modelClass.collection)) {
92
+ modelClass.collection = collection;
93
+ }
94
+
91
95
  await trackSoukaiModelsCollection(modelClass, collection);
92
96
  }
93
97
 
@@ -107,24 +111,24 @@ export async function updateRemoteModel(localModel: SolidModel): Promise<void> {
107
111
  },
108
112
  });
109
113
 
114
+ await DocumentsCache.forget(localModel.requireDocumentUrl());
115
+
110
116
  if (Cloud.autoPush && Cloud.ready && !Cloud.syncing) {
111
117
  SyncQueue.push(localModel);
112
118
  }
113
119
  }
114
120
 
115
- export function clearLocalModelUpdates(models?: SolidModel[], malformedDocuments?: Set<string>): void {
116
- const syncedModelUrls = models?.map((model) => toString(model.getDeletedPrimaryKey() ?? model.getPrimaryKey()));
121
+ export function clearLocalModelUpdates(syncedModelUrls: Set<string>, documentsWithErrors?: Set<string>): void {
117
122
  const localModelUpdates = {} as Record<string, number>;
123
+ const syncedModelUrlsArray = Array.from(syncedModelUrls);
118
124
 
119
125
  for (const [url, count] of Object.entries(Cloud.localModelUpdates)) {
120
126
  const documentUrl = urlRoute(url);
121
127
  const wasSynced =
122
- !syncedModelUrls ||
123
- syncedModelUrls.some(
124
- (modelUrl) => url === modelUrl || (modelUrl.endsWith('/') && url.startsWith(modelUrl)),
125
- );
128
+ syncedModelUrls.has(url) ||
129
+ syncedModelUrlsArray.some((modelUrl) => modelUrl.endsWith('/') && url.startsWith(modelUrl));
126
130
 
127
- if (wasSynced && !malformedDocuments?.has(documentUrl)) {
131
+ if (wasSynced && !documentsWithErrors?.has(documentUrl)) {
128
132
  continue;
129
133
  }
130
134
 
@@ -177,24 +181,27 @@ export function cloneRemoteModel(remoteModel: SolidModel): SolidModel {
177
181
  export async function completeRemoteModels(
178
182
  localModels: SolidModel[],
179
183
  remoteModels: SolidModel[],
180
- malformedDocuments: Set<string>,
184
+ documentsWithErrors: Set<string>,
181
185
  ): Promise<SolidModel[]> {
182
186
  const RemoteTombstone = getRemoteClass(Tombstone);
183
187
  const remoteModelUrls = remoteModels.map((remoteModel) => remoteModel.url);
184
188
  const missingModelDocumentUrls = localModels
185
189
  .filter((localModel) => !remoteModelUrls.includes(localModel.url))
186
190
  .map((localModel) => localModel.requireDocumentUrl())
187
- .filter((documentUrl) => !malformedDocuments.has(documentUrl));
191
+ .filter((documentUrl) => !documentsWithErrors.has(documentUrl));
188
192
  const tombstones = await RemoteTombstone.all({ $in: missingModelDocumentUrls });
189
193
 
190
194
  return remoteModels.concat(tombstones);
191
195
  }
192
196
 
193
197
  export function ignoreModelUpdates(localModel: SolidModel): boolean {
194
- const updatedAt = localModel.metadata.updatedAt?.getTime();
198
+ const documentModels = localModel.getDocumentModels();
199
+ const documentOperations = documentModels.flatMap(
200
+ (model) => model.operations?.map((operation) => [model, operation] as [SolidModel, Operation]) ?? [],
201
+ );
195
202
 
196
- return !localModel.operations.some((operation) => {
197
- if (operation.date.getTime() !== updatedAt) {
203
+ return !documentOperations.some(([model, operation]) => {
204
+ if (operation.date.getTime() !== model.metadata?.updatedAt?.getTime()) {
198
205
  return false;
199
206
  }
200
207
 
@@ -202,7 +209,7 @@ export function ignoreModelUpdates(localModel: SolidModel): boolean {
202
209
  return true;
203
210
  }
204
211
 
205
- return !localModel.ignoreRdfPropertyHistory(operation.property, true);
212
+ return !model.ignoreRdfPropertyHistory(operation.property, true);
206
213
  });
207
214
  }
208
215
 
@@ -211,7 +218,9 @@ export function getLocalModel(remoteModel: SolidModel, localModels: ObjectsMap<S
211
218
  }
212
219
 
213
220
  export function getLocalModels(): SolidModel[] {
214
- return [...Cloud.registeredModels].map(({ modelClass }) => getTrackedModels(modelClass)).flat();
221
+ return [...Cloud.registeredModels]
222
+ .map(({ modelClass }) => getTrackedModels(modelClass, { includeSoftDeleted: true }))
223
+ .flat();
215
224
  }
216
225
 
217
226
  export function getSchemaMigrations(): Map<SolidModelConstructor, SolidModelConstructor | SolidSchemaDefinition> {
@@ -232,12 +241,27 @@ export function getRemoteClass<T extends SolidModelConstructor>(localClass: T):
232
241
  return (remoteClasses.get(localClass) as T) ?? makeRemoteClass(localClass);
233
242
  }
234
243
 
235
- export function getRemoteContainersCollection(path?: string): string {
244
+ export function getRemoteContainersCollection(modelClass: SolidModelConstructor, path?: string): string {
236
245
  const rootStorage = Solid.requireUser().storageUrls[0];
246
+ const containedClass =
247
+ isContainerClass(modelClass) &&
248
+ getContainerRelations(modelClass)
249
+ .map((relation) => {
250
+ const relatedClass = modelClass
251
+ .instance()
252
+ .requireRelation<SolidContainsRelation>(relation).relatedClass;
253
+
254
+ if (isContainerClass(relatedClass)) {
255
+ return null;
256
+ }
257
+
258
+ return relatedClass;
259
+ })
260
+ .filter(Boolean)[0];
261
+
262
+ path ??= `/${(containedClass || modelClass).modelName.toLowerCase()}s/`;
237
263
 
238
- return path
239
- ? urlResolveDirectory((path.startsWith('/') ? rootStorage.slice(0, -1) : rootStorage) + path)
240
- : rootStorage;
264
+ return urlResolveDirectory((path.startsWith('/') ? rootStorage.slice(0, -1) : rootStorage) + path);
241
265
  }
242
266
 
243
267
  export function getRemoteModel(localModel: SolidModel, remoteModels: ObjectsMap<SolidModel>): SolidModel {
@@ -6,13 +6,15 @@ import {
6
6
  getLocationQueryParameter,
7
7
  hasLocationQueryParameter,
8
8
  isArray,
9
+ isTesting,
9
10
  parseBoolean,
10
11
  } from '@noeldemartin/utils';
11
- import { App, Errors, Events, dispatch, translateWithDefault } from '@aerogel/core';
12
+ import { Errors, Events, dispatch, translateWithDefault } from '@aerogel/core';
12
13
  import { Solid } from '@aerogel/plugin-solid';
13
- import { SolidModel, Tombstone, isContainer, isContainerClass } from 'soukai-solid';
14
+ import { SolidContainer, SolidModel, Tombstone, isContainer, isContainerClass, isSolidModel } from 'soukai-solid';
14
15
  import { getTrackedModels, trackModels, trackModelsCollection } from '@aerogel/plugin-soukai';
15
16
  import { watchEffect } from 'vue';
17
+ import { getBootedModels } from 'soukai';
16
18
  import type { Authenticator } from '@aerogel/plugin-solid';
17
19
  import type { Engine } from 'soukai';
18
20
  import type { JobListener } from '@aerogel/core';
@@ -27,6 +29,7 @@ import {
27
29
  updateRemoteModel,
28
30
  } from '@aerogel/plugin-local-first/lib/mirroring';
29
31
  import Backup from '@aerogel/plugin-local-first/jobs/Backup';
32
+ import DocumentsCache from '@aerogel/plugin-local-first/services/DocumentsCache';
30
33
  import Migrate from '@aerogel/plugin-local-first/jobs/Migrate';
31
34
  import Sync from '@aerogel/plugin-local-first/jobs/Sync';
32
35
  import SyncQueue from '@aerogel/plugin-local-first/lib/SyncQueue';
@@ -43,9 +46,10 @@ export interface RegisterOptions {
43
46
  }
44
47
 
45
48
  export interface SyncOptions extends JobListener {
46
- refreshUserProfile?: boolean;
47
49
  model?: SolidModel;
48
50
  models?: SolidModel[];
51
+ refreshUserProfile?: boolean;
52
+ syncDirty?: boolean;
49
53
  }
50
54
 
51
55
  export class CloudService extends Service {
@@ -116,8 +120,8 @@ export class CloudService extends Service {
116
120
  }
117
121
 
118
122
  await this.asyncLock.run(async () => {
123
+ let models = options.model ? [options.model] : options.models;
119
124
  const start = Date.now();
120
- const models = options.model ? [options.model] : options.models;
121
125
 
122
126
  this.syncError = null;
123
127
  this.status = CloudStatus.Syncing;
@@ -131,23 +135,33 @@ export class CloudService extends Service {
131
135
  await Solid.refreshUserProfile();
132
136
  }
133
137
 
134
- this.syncJob = new Sync(models);
138
+ const syncDirty = options.syncDirty ?? true;
139
+ let syncs = 0;
135
140
 
136
- this.syncJob.listeners.add(options);
141
+ do {
142
+ this.syncJob = new Sync(models);
137
143
 
138
- await dispatch(this.syncJob);
144
+ this.syncJob.listeners.add(options);
139
145
 
140
- if (this.syncJob.cancelled) {
141
- return;
142
- }
146
+ await dispatch(this.syncJob);
143
147
 
144
- if (!App.testing) {
148
+ if (this.syncJob.cancelled) {
149
+ return;
150
+ }
151
+
152
+ syncs++;
153
+ models = this.getDirtyLocalModels().filter(
154
+ (model) => !this.syncJob?.documentsWithErrors.has(model.requireDocumentUrl()),
155
+ );
156
+ } while (syncDirty && models.length > 0 && syncs < 3);
157
+
158
+ if (!isTesting('unit')) {
145
159
  await after({ milliseconds: Math.max(500, 1000 - (Date.now() - start)) });
146
160
  }
147
161
 
148
162
  this.syncJob = null;
149
163
  } catch (error) {
150
- if (App.testing) {
164
+ if (isTesting()) {
151
165
  throw error;
152
166
  }
153
167
 
@@ -226,17 +240,18 @@ export class CloudService extends Service {
226
240
  }
227
241
 
228
242
  this.whenReady(() => {
229
- if (!isContainerClass(modelClass)) {
230
- return;
231
- }
243
+ const [existingCollection] = this.modelCollections[modelClass.modelName] ?? [];
232
244
 
233
- modelClass.collection = getRemoteContainersCollection(options.path);
245
+ modelClass.useSoftDeletes(true);
246
+
247
+ modelClass.collection = existingCollection ?? getRemoteContainersCollection(modelClass, options.path);
234
248
  });
235
249
 
236
250
  await trackModels(modelClass, {
237
- created: (model) => createRemoteModel(model),
238
- updated: (model) => updateRemoteModel(model),
239
- deleted: (model) => clearLocalModelUpdates([model]),
251
+ created: (model) => this.ready && createRemoteModel(model),
252
+ updated: (model) => this.ready && updateRemoteModel(model),
253
+ deleted: (model) =>
254
+ this.ready && clearLocalModelUpdates(new Set([model.url ?? model.getDeletedPrimaryKey()])),
240
255
  });
241
256
 
242
257
  if (isContainerClass(modelClass)) {
@@ -245,9 +260,13 @@ export class CloudService extends Service {
245
260
  .instance()
246
261
  .requireRelation<SolidContainsRelation>(relation).relatedClass;
247
262
 
248
- relatedClass.on('created', (model) => createRemoteModel(model));
249
- relatedClass.on('updated', (model) => updateRemoteModel(model));
250
- relatedClass.on('deleted', (model) => clearLocalModelUpdates([model]));
263
+ relatedClass.on('created', (model) => this.ready && createRemoteModel(model));
264
+ relatedClass.on('updated', (model) => this.ready && updateRemoteModel(model));
265
+ relatedClass.on(
266
+ 'deleted',
267
+ (model) =>
268
+ this.ready && clearLocalModelUpdates(new Set([model.url ?? model.getDeletedPrimaryKey()])),
269
+ );
251
270
  });
252
271
  }
253
272
 
@@ -271,7 +290,7 @@ export class CloudService extends Service {
271
290
  continue;
272
291
  }
273
292
 
274
- return getRemoteContainersCollection(registration.path);
293
+ return getRemoteContainersCollection(registration.modelClass, registration.path);
275
294
  }
276
295
 
277
296
  throw new Error(`Failed resolving remote collection for '${modelClass.modelName}' model`);
@@ -298,6 +317,9 @@ export class CloudService extends Service {
298
317
 
299
318
  this.pollingInterval = setInterval(() => this.sync(), this.pollingMinutes * 60 * 1000);
300
319
  });
320
+
321
+ await this.bustDocumentsCache();
322
+ await this.registerModels();
301
323
  }
302
324
 
303
325
  protected getDirtyLocalModels(): SolidModel[] {
@@ -305,7 +327,7 @@ export class CloudService extends Service {
305
327
  const dirtyLocalModels = [];
306
328
 
307
329
  for (const { modelClass } of this.registeredModels) {
308
- for (const model of getTrackedModels(modelClass)) {
330
+ for (const model of getTrackedModels(modelClass, { includeSoftDeleted: true })) {
309
331
  dirtyLocalModels.push(...this.getModelsIn(urls, model));
310
332
  }
311
333
  }
@@ -362,7 +384,7 @@ export class CloudService extends Service {
362
384
  }
363
385
 
364
386
  const localCollection = modelClass.collection;
365
- const remoteCollection = getRemoteContainersCollection(path);
387
+ const remoteCollection = getRemoteContainersCollection(modelClass, path);
366
388
 
367
389
  modelClass.collection = remoteCollection;
368
390
 
@@ -398,6 +420,8 @@ export class CloudService extends Service {
398
420
  this.status = CloudStatus.Online;
399
421
  this.engine = authenticator.engine;
400
422
 
423
+ getRemoteClass(SolidContainer).setEngine(this.engine);
424
+
401
425
  for (const relatedClass of relatedClasses) {
402
426
  getRemoteClass(relatedClass).setEngine(this.engine);
403
427
  }
@@ -405,6 +429,7 @@ export class CloudService extends Service {
405
429
 
406
430
  protected logout(): void {
407
431
  this.setState({
432
+ autoPush: true,
408
433
  localModelUpdates: {},
409
434
  migrationDismissed: false,
410
435
  migrationJob: null,
@@ -413,10 +438,13 @@ export class CloudService extends Service {
413
438
  pollingEnabled: true,
414
439
  pollingMinutes: 10,
415
440
  ready: false,
441
+ schemaMigrations: new Map(),
416
442
  setupDismissed: false,
417
443
  setupOngoing: false,
418
444
  startupSync: true,
419
445
  status: CloudStatus.Disconnected,
446
+ syncError: null,
447
+ syncJob: null,
420
448
  });
421
449
  }
422
450
 
@@ -432,6 +460,30 @@ export class CloudService extends Service {
432
460
  return this.startupSync;
433
461
  }
434
462
 
463
+ private async bustDocumentsCache(): Promise<void> {
464
+ if (
465
+ !hasLocationQueryParameter('bustDocumentsCache') ||
466
+ !parseBoolean(getLocationQueryParameter('bustDocumentsCache'))
467
+ ) {
468
+ return;
469
+ }
470
+
471
+ await DocumentsCache.booted;
472
+ await DocumentsCache.clear();
473
+ }
474
+
475
+ private async registerModels(): Promise<void> {
476
+ const models = getBootedModels();
477
+
478
+ for (const model of models.values()) {
479
+ if (!isSolidModel(model) || !model.cloud) {
480
+ continue;
481
+ }
482
+
483
+ await this.register(model, typeof model.cloud === 'boolean' ? {} : model.cloud);
484
+ }
485
+ }
486
+
435
487
  }
436
488
 
437
489
  export default facade(CloudService);
@@ -448,3 +500,10 @@ declare module '@aerogel/core' {
448
500
  'cloud:sync-completed': SolidModel[] | undefined;
449
501
  }
450
502
  }
503
+
504
+ declare module 'soukai-solid' {
505
+ // eslint-disable-next-line @typescript-eslint/no-shadow
506
+ namespace SolidModel {
507
+ export const cloud: boolean | RegisterOptions | undefined;
508
+ }
509
+ }
@@ -1,9 +1,9 @@
1
- import { Events, Service } from '@aerogel/core';
1
+ import { Events, Service, appNamespace } from '@aerogel/core';
2
2
  import { IndexedDBMap, facade } from '@noeldemartin/utils';
3
3
 
4
4
  export class DocumentsCacheService extends Service {
5
5
 
6
- private cache = new IndexedDBMap<{ modifiedAt: number }>('documents-cache');
6
+ private cache = new IndexedDBMap<{ modifiedAt: number }>(`${appNamespace()}-documents-cache`);
7
7
 
8
8
  public async isFresh(documentUrl: string, modifiedAt: Date | number): Promise<boolean> {
9
9
  const meta = await this.cache.get(documentUrl);
@@ -22,6 +22,10 @@ export class DocumentsCacheService extends Service {
22
22
  await this.cache.delete(documentUrl);
23
23
  }
24
24
 
25
+ public async clear(): Promise<void> {
26
+ await this.cache.clear();
27
+ }
28
+
25
29
  protected override async boot(): Promise<void> {
26
30
  Events.on('auth:logout', async () => {
27
31
  await this.cache.clear();
@@ -128,7 +128,7 @@ export function containerResponse(
128
128
  `);
129
129
  }
130
130
 
131
- export function movieResponse(title: string): Response {
131
+ export function movieResponse(title: string = 'Spirited Away'): Response {
132
132
  return FakeResponse.success(`
133
133
  @prefix schema: <https://schema.org/>.
134
134
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@@ -1,12 +1,13 @@
1
1
  import 'fake-indexeddb/auto';
2
2
 
3
- import { installVitestSolidMatchers } from '@noeldemartin/solid-utils/testing';
4
- import { FakeLocalStorage, FakeServer, setupFacadeMocks } from '@noeldemartin/testing';
3
+ import { installVitestSolidMatchers } from '@noeldemartin/solid-utils/vitest';
4
+ import { FakeLocalStorage, FakeServer, mock, setupFacadeMocks } from '@noeldemartin/testing';
5
5
  import { resetAsyncMemo } from '@noeldemartin/utils';
6
6
  import { beforeEach, vi } from 'vitest';
7
7
 
8
8
  setupFacadeMocks();
9
9
  installVitestSolidMatchers();
10
+ globalThis.location = mock<Location>({ href: 'https://example.com' });
10
11
 
11
12
  beforeEach(async () => {
12
13
  const { Solid } = await import('@aerogel/plugin-solid');
@@ -0,0 +1,9 @@
1
+ import { FieldType } from 'soukai';
2
+ import { defineSolidModelSchema } from 'soukai-solid';
3
+
4
+ export default defineSolidModelSchema({
5
+ rdfContext: 'https://schema.org/',
6
+ fields: {
7
+ name: FieldType.String,
8
+ },
9
+ });
@@ -0,0 +1,18 @@
1
+ import type { SolidHasManyRelation } from 'soukai-solid';
2
+ import type { Relation } from 'soukai';
3
+
4
+ import Model from './Person.schema';
5
+ import Post from './Post';
6
+
7
+ export default class Person extends Model {
8
+
9
+ public static override timestamps = false;
10
+
11
+ declare public posts?: Post[];
12
+ declare public relatedPosts: SolidHasManyRelation<Person, Post, typeof Post>;
13
+
14
+ public postsRelationship(): Relation {
15
+ return this.hasMany(Post, 'authorUrl');
16
+ }
17
+
18
+ }
@@ -0,0 +1,17 @@
1
+ import { FieldType } from 'soukai';
2
+ import { defineSolidModelSchema } from 'soukai-solid';
3
+
4
+ export default defineSolidModelSchema({
5
+ rdfContext: 'https://schema.org/',
6
+ rdfsClass: 'Article',
7
+ fields: {
8
+ title: {
9
+ type: FieldType.String,
10
+ rdfProperty: 'name',
11
+ },
12
+ authorUrl: {
13
+ type: FieldType.Key,
14
+ rdfProperty: 'author',
15
+ },
16
+ },
17
+ });
@@ -0,0 +1,18 @@
1
+ import type { Relation } from 'soukai';
2
+ import type { SolidBelongsToOneRelation } from 'soukai-solid';
3
+
4
+ import Model from './Post.schema';
5
+ import Person from './Person';
6
+
7
+ export default class Post extends Model {
8
+
9
+ public static override timestamps = false;
10
+
11
+ declare public author?: Person;
12
+ declare public relatedAuthor: SolidBelongsToOneRelation<Post, Person, typeof Person>;
13
+
14
+ public authorRelationship(): Relation {
15
+ return this.belongsToOne(Person, 'authorUrl');
16
+ }
17
+
18
+ }
@@ -1,9 +0,0 @@
1
- import { events as n, fetch as t, handleIncomingRedirect as c, login as g, logout as i } from "@inrupt/solid-client-authn-browser";
2
- export {
3
- n as events,
4
- t as fetch,
5
- c as handleIncomingRedirect,
6
- g as login,
7
- i as logout
8
- };
9
- //# sourceMappingURL=InruptAuthenticator.lazy-BvpQLGoj-BvpQLGoj.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"InruptAuthenticator.lazy-BvpQLGoj-BvpQLGoj.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,5 +0,0 @@
1
- import { default as l } from "solid-auth-client";
2
- export {
3
- l as SolidAuthClient
4
- };
5
- //# sourceMappingURL=LegacyAuthenticator.lazy-ChFFqP14-ChFFqP14.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"LegacyAuthenticator.lazy-ChFFqP14-ChFFqP14.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,31 +0,0 @@
1
- <template>
2
- <AGSolidStatus>
3
- <template #logged-in="{ session }: IAGSolidStatusLoggedInSlotProps">
4
- <AGMarkdown v-if="$cloud.syncing" :text="$td('cloud.syncing', 'Syncing...')" />
5
- <div v-else class="flex flex-col gap-3">
6
- <AGMarkdown
7
- :text="
8
- $td('solid.loggedIn', 'You are logged in as [{userName}]({userWebId})', {
9
- userName: session.user.name ?? session.user.webId,
10
- userWebId: session.user.webId,
11
- })
12
- "
13
- />
14
- <AGButton @click="$cloud.sync(), $emit('sync')">
15
- {{ $td('cloud.sync', 'Synchronize') }}
16
- </AGButton>
17
- <AGButton @click="$solid.logout()">
18
- {{ $td('solid.logout', 'Logout') }}
19
- </AGButton>
20
- </div>
21
- </template>
22
- </AGSolidStatus>
23
- </template>
24
-
25
- <script setup lang="ts">
26
- import { AGButton, AGMarkdown } from '@aerogel/core';
27
- import { AGSolidStatus } from '@aerogel/plugin-solid';
28
- import type { IAGSolidStatusLoggedInSlotProps } from '@aerogel/plugin-solid';
29
-
30
- defineEmits(['sync']);
31
- </script>