@pylonsync/sdk 0.22.43 → 0.22.44
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/dist/index.d.ts +34 -0
- package/package.json +1 -1
- package/src/entity-after-edit.test.ts +29 -0
- package/src/index.ts +41 -0
package/dist/index.d.ts
CHANGED
|
@@ -367,6 +367,11 @@ export interface EntityDefinition {
|
|
|
367
367
|
* in the audit log as `retention.delete`. See {@link RetentionRule}.
|
|
368
368
|
*/
|
|
369
369
|
retention?: RetentionRule;
|
|
370
|
+
/**
|
|
371
|
+
* Run an internal function after edits to a row land. See
|
|
372
|
+
* {@link AfterEditHook}.
|
|
373
|
+
*/
|
|
374
|
+
afterEdit?: AfterEditHook;
|
|
370
375
|
/**
|
|
371
376
|
* Per-row CRDT (Loro) documents. Default `true`: every write merges through
|
|
372
377
|
* a document, and offline edits from several devices converge. Set `false`
|
|
@@ -427,6 +432,27 @@ export interface RetentionRule {
|
|
|
427
432
|
after?: string;
|
|
428
433
|
hold?: string;
|
|
429
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* An internal function that runs after edits to a row land: a search index,
|
|
437
|
+
* a preview, a review queue.
|
|
438
|
+
*
|
|
439
|
+
* ```ts
|
|
440
|
+
* entity("Document", { … }, { afterEdit: { fn: "indexDocument", delaySeconds: 5 } })
|
|
441
|
+
* ```
|
|
442
|
+
*
|
|
443
|
+
* Any committed write to a row (an insert, an update, a live edit, a
|
|
444
|
+
* delete) schedules `fn` with `{ id }`, to run at the end of the row's
|
|
445
|
+
* current `delaySeconds` window; every edit in that window shares the run.
|
|
446
|
+
* A row edited without pause gets one run per window. `fn` must be
|
|
447
|
+
* `internal: true`; it runs without a user, like a cron target, and reads
|
|
448
|
+
* the row itself (it may have been deleted). Runs are jobs: they survive a
|
|
449
|
+
* restart, retry on failure, and show in `pylon jobs`.
|
|
450
|
+
*/
|
|
451
|
+
export interface AfterEditHook {
|
|
452
|
+
fn: string;
|
|
453
|
+
/** The window, in seconds. Default 5. */
|
|
454
|
+
delaySeconds?: number;
|
|
455
|
+
}
|
|
430
456
|
export declare function entity(name: string, fields: Record<string, FieldBuilder>, options?: {
|
|
431
457
|
indexes?: IndexDefinition[];
|
|
432
458
|
relations?: RelationDefinition[];
|
|
@@ -444,6 +470,9 @@ export declare function entity(name: string, fields: Record<string, FieldBuilder
|
|
|
444
470
|
/** Delete rows past a retention period. Mirrors
|
|
445
471
|
* {@link EntityDefinition.retention}. */
|
|
446
472
|
retention?: RetentionRule;
|
|
473
|
+
/** Run an internal function after edits land. Mirrors
|
|
474
|
+
* {@link EntityDefinition.afterEdit}. */
|
|
475
|
+
afterEdit?: AfterEditHook;
|
|
447
476
|
}): EntityDefinition;
|
|
448
477
|
export declare function relation(def: RelationDefinition): RelationDefinition;
|
|
449
478
|
export { defineRoute } from "./route";
|
|
@@ -635,6 +664,11 @@ export interface ManifestEntity {
|
|
|
635
664
|
audit?: boolean;
|
|
636
665
|
/** Retention rule; omitted when unset. */
|
|
637
666
|
retention?: RetentionRule;
|
|
667
|
+
/** After-edit hook; omitted when unset. */
|
|
668
|
+
after_edit?: {
|
|
669
|
+
function: string;
|
|
670
|
+
delay_secs: number;
|
|
671
|
+
};
|
|
638
672
|
}
|
|
639
673
|
export interface ManifestRoute {
|
|
640
674
|
path: string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { entitiesToManifest, entity, field } from "./index";
|
|
3
|
+
|
|
4
|
+
describe("entity afterEdit option", () => {
|
|
5
|
+
test("reaches the manifest as after_edit, with the default window", () => {
|
|
6
|
+
const [a, b] = entitiesToManifest([
|
|
7
|
+
entity("Doc", { title: field.string() }, { afterEdit: { fn: "indexDoc" } }),
|
|
8
|
+
entity("Note", { title: field.string() }, { afterEdit: { fn: "indexNote", delaySeconds: 30 } }),
|
|
9
|
+
]);
|
|
10
|
+
expect(a.after_edit).toEqual({ function: "indexDoc", delay_secs: 5 });
|
|
11
|
+
expect(b.after_edit).toEqual({ function: "indexNote", delay_secs: 30 });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("is omitted when unset", () => {
|
|
15
|
+
const [m] = entitiesToManifest([entity("Doc", { title: field.string() })]);
|
|
16
|
+
expect("after_edit" in m).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("refuses a window that is not a whole number of seconds, and a missing function", () => {
|
|
20
|
+
for (const delaySeconds of [0, 1.5, -1]) {
|
|
21
|
+
expect(() =>
|
|
22
|
+
entitiesToManifest([entity("Doc", { t: field.string() }, { afterEdit: { fn: "f", delaySeconds } })]),
|
|
23
|
+
).toThrow("whole number of seconds");
|
|
24
|
+
}
|
|
25
|
+
expect(() => entitiesToManifest([entity("Doc", { t: field.string() }, { afterEdit: { fn: "" } })])).toThrow(
|
|
26
|
+
"afterEdit needs fn",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -476,6 +476,11 @@ export interface EntityDefinition {
|
|
|
476
476
|
* in the audit log as `retention.delete`. See {@link RetentionRule}.
|
|
477
477
|
*/
|
|
478
478
|
retention?: RetentionRule;
|
|
479
|
+
/**
|
|
480
|
+
* Run an internal function after edits to a row land. See
|
|
481
|
+
* {@link AfterEditHook}.
|
|
482
|
+
*/
|
|
483
|
+
afterEdit?: AfterEditHook;
|
|
479
484
|
/**
|
|
480
485
|
* Per-row CRDT (Loro) documents. Default `true`: every write merges through
|
|
481
486
|
* a document, and offline edits from several devices converge. Set `false`
|
|
@@ -539,6 +544,28 @@ export interface RetentionRule {
|
|
|
539
544
|
hold?: string;
|
|
540
545
|
}
|
|
541
546
|
|
|
547
|
+
/**
|
|
548
|
+
* An internal function that runs after edits to a row land: a search index,
|
|
549
|
+
* a preview, a review queue.
|
|
550
|
+
*
|
|
551
|
+
* ```ts
|
|
552
|
+
* entity("Document", { … }, { afterEdit: { fn: "indexDocument", delaySeconds: 5 } })
|
|
553
|
+
* ```
|
|
554
|
+
*
|
|
555
|
+
* Any committed write to a row (an insert, an update, a live edit, a
|
|
556
|
+
* delete) schedules `fn` with `{ id }`, to run at the end of the row's
|
|
557
|
+
* current `delaySeconds` window; every edit in that window shares the run.
|
|
558
|
+
* A row edited without pause gets one run per window. `fn` must be
|
|
559
|
+
* `internal: true`; it runs without a user, like a cron target, and reads
|
|
560
|
+
* the row itself (it may have been deleted). Runs are jobs: they survive a
|
|
561
|
+
* restart, retry on failure, and show in `pylon jobs`.
|
|
562
|
+
*/
|
|
563
|
+
export interface AfterEditHook {
|
|
564
|
+
fn: string;
|
|
565
|
+
/** The window, in seconds. Default 5. */
|
|
566
|
+
delaySeconds?: number;
|
|
567
|
+
}
|
|
568
|
+
|
|
542
569
|
export function entity(
|
|
543
570
|
name: string,
|
|
544
571
|
fields: Record<string, FieldBuilder>,
|
|
@@ -559,6 +586,9 @@ export function entity(
|
|
|
559
586
|
/** Delete rows past a retention period. Mirrors
|
|
560
587
|
* {@link EntityDefinition.retention}. */
|
|
561
588
|
retention?: RetentionRule;
|
|
589
|
+
/** Run an internal function after edits land. Mirrors
|
|
590
|
+
* {@link EntityDefinition.afterEdit}. */
|
|
591
|
+
afterEdit?: AfterEditHook;
|
|
562
592
|
},
|
|
563
593
|
): EntityDefinition {
|
|
564
594
|
return {
|
|
@@ -571,6 +601,7 @@ export function entity(
|
|
|
571
601
|
crdt: options?.crdt,
|
|
572
602
|
audit: options?.audit,
|
|
573
603
|
retention: options?.retention,
|
|
604
|
+
afterEdit: options?.afterEdit,
|
|
574
605
|
};
|
|
575
606
|
}
|
|
576
607
|
|
|
@@ -822,6 +853,8 @@ export interface ManifestEntity {
|
|
|
822
853
|
audit?: boolean;
|
|
823
854
|
/** Retention rule; omitted when unset. */
|
|
824
855
|
retention?: RetentionRule;
|
|
856
|
+
/** After-edit hook; omitted when unset. */
|
|
857
|
+
after_edit?: { function: string; delay_secs: number };
|
|
825
858
|
}
|
|
826
859
|
|
|
827
860
|
export interface ManifestRoute {
|
|
@@ -1376,6 +1409,14 @@ export function entitiesToManifest(
|
|
|
1376
1409
|
...(e.retention.hold ? { hold: e.retention.hold } : {}),
|
|
1377
1410
|
};
|
|
1378
1411
|
}
|
|
1412
|
+
if (e.afterEdit) {
|
|
1413
|
+
const delay = e.afterEdit.delaySeconds ?? 5;
|
|
1414
|
+
if (!e.afterEdit.fn) throw new Error(`${e.name}: afterEdit needs fn`);
|
|
1415
|
+
if (!Number.isInteger(delay) || delay < 1) {
|
|
1416
|
+
throw new Error(`${e.name}: afterEdit.delaySeconds must be a whole number of seconds, at least 1`);
|
|
1417
|
+
}
|
|
1418
|
+
result.after_edit = { function: e.afterEdit.fn, delay_secs: delay };
|
|
1419
|
+
}
|
|
1379
1420
|
return result;
|
|
1380
1421
|
});
|
|
1381
1422
|
}
|