@pgpm/jobs 0.4.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 (115) hide show
  1. package/LICENSE +22 -0
  2. package/Makefile +6 -0
  3. package/README.md +5 -0
  4. package/__tests__/__snapshots__/jobs.test.ts.snap +16 -0
  5. package/__tests__/jobs.test.ts +139 -0
  6. package/deploy/schemas/app_jobs/helpers/json_build_object_apply.sql +28 -0
  7. package/deploy/schemas/app_jobs/procedures/add_job.sql +65 -0
  8. package/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql +66 -0
  9. package/deploy/schemas/app_jobs/procedures/complete_job.sql +32 -0
  10. package/deploy/schemas/app_jobs/procedures/complete_jobs.sql +19 -0
  11. package/deploy/schemas/app_jobs/procedures/do_notify.sql +16 -0
  12. package/deploy/schemas/app_jobs/procedures/fail_job.sql +41 -0
  13. package/deploy/schemas/app_jobs/procedures/get_job.sql +77 -0
  14. package/deploy/schemas/app_jobs/procedures/get_scheduled_job.sql +46 -0
  15. package/deploy/schemas/app_jobs/procedures/permanently_fail_jobs.sql +24 -0
  16. package/deploy/schemas/app_jobs/procedures/release_jobs.sql +34 -0
  17. package/deploy/schemas/app_jobs/procedures/release_scheduled_jobs.sql +26 -0
  18. package/deploy/schemas/app_jobs/procedures/reschedule_jobs.sql +26 -0
  19. package/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql +67 -0
  20. package/deploy/schemas/app_jobs/schema.sql +7 -0
  21. package/deploy/schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator.sql +12 -0
  22. package/deploy/schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx.sql +8 -0
  23. package/deploy/schemas/app_jobs/tables/job_queues/table.sql +12 -0
  24. package/deploy/schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_administrator.sql +12 -0
  25. package/deploy/schemas/app_jobs/tables/jobs/indexes/jobs_locked_by_idx.sql +8 -0
  26. package/deploy/schemas/app_jobs/tables/jobs/indexes/priority_run_at_id_idx.sql +8 -0
  27. package/deploy/schemas/app_jobs/tables/jobs/table.sql +26 -0
  28. package/deploy/schemas/app_jobs/tables/jobs/triggers/decrease_job_queue_count.sql +42 -0
  29. package/deploy/schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count.sql +32 -0
  30. package/deploy/schemas/app_jobs/tables/jobs/triggers/notify_worker.sql +13 -0
  31. package/deploy/schemas/app_jobs/tables/jobs/triggers/timestamps.sql +20 -0
  32. package/deploy/schemas/app_jobs/tables/scheduled_jobs/grants/grant_select_insert_update_delete_to_administrator.sql +12 -0
  33. package/deploy/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_locked_by_idx.sql +8 -0
  34. package/deploy/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_priority_id_idx.sql +8 -0
  35. package/deploy/schemas/app_jobs/tables/scheduled_jobs/table.sql +26 -0
  36. package/deploy/schemas/app_jobs/tables/scheduled_jobs/triggers/notify_scheduled_job.sql +12 -0
  37. package/deploy/schemas/app_jobs/triggers/tg_add_job_with_fields.sql +50 -0
  38. package/deploy/schemas/app_jobs/triggers/tg_add_job_with_row.sql +26 -0
  39. package/deploy/schemas/app_jobs/triggers/tg_add_job_with_row_id.sql +26 -0
  40. package/deploy/schemas/app_jobs/triggers/tg_update_timestamps.sql +21 -0
  41. package/jest.config.js +15 -0
  42. package/launchql-jobs.control +8 -0
  43. package/launchql.plan +38 -0
  44. package/package.json +29 -0
  45. package/revert/schemas/app_jobs/helpers/json_build_object_apply.sql +7 -0
  46. package/revert/schemas/app_jobs/procedures/add_job.sql +7 -0
  47. package/revert/schemas/app_jobs/procedures/add_scheduled_job.sql +7 -0
  48. package/revert/schemas/app_jobs/procedures/complete_job.sql +7 -0
  49. package/revert/schemas/app_jobs/procedures/complete_jobs.sql +7 -0
  50. package/revert/schemas/app_jobs/procedures/do_notify.sql +7 -0
  51. package/revert/schemas/app_jobs/procedures/fail_job.sql +7 -0
  52. package/revert/schemas/app_jobs/procedures/get_job.sql +7 -0
  53. package/revert/schemas/app_jobs/procedures/get_scheduled_job.sql +7 -0
  54. package/revert/schemas/app_jobs/procedures/permanently_fail_jobs.sql +7 -0
  55. package/revert/schemas/app_jobs/procedures/release_jobs.sql +7 -0
  56. package/revert/schemas/app_jobs/procedures/release_scheduled_jobs.sql +7 -0
  57. package/revert/schemas/app_jobs/procedures/reschedule_jobs.sql +7 -0
  58. package/revert/schemas/app_jobs/procedures/run_scheduled_job.sql +7 -0
  59. package/revert/schemas/app_jobs/schema.sql +7 -0
  60. package/revert/schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator.sql +7 -0
  61. package/revert/schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx.sql +7 -0
  62. package/revert/schemas/app_jobs/tables/job_queues/table.sql +7 -0
  63. package/revert/schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_administrator.sql +7 -0
  64. package/revert/schemas/app_jobs/tables/jobs/indexes/jobs_locked_by_idx.sql +7 -0
  65. package/revert/schemas/app_jobs/tables/jobs/indexes/priority_run_at_id_idx.sql +7 -0
  66. package/revert/schemas/app_jobs/tables/jobs/table.sql +7 -0
  67. package/revert/schemas/app_jobs/tables/jobs/triggers/decrease_job_queue_count.sql +7 -0
  68. package/revert/schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count.sql +7 -0
  69. package/revert/schemas/app_jobs/tables/jobs/triggers/notify_worker.sql +5 -0
  70. package/revert/schemas/app_jobs/tables/jobs/triggers/timestamps.sql +9 -0
  71. package/revert/schemas/app_jobs/tables/scheduled_jobs/grants/grant_select_insert_update_delete_to_administrator.sql +7 -0
  72. package/revert/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_locked_by_idx.sql +7 -0
  73. package/revert/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_priority_id_idx.sql +7 -0
  74. package/revert/schemas/app_jobs/tables/scheduled_jobs/table.sql +7 -0
  75. package/revert/schemas/app_jobs/tables/scheduled_jobs/triggers/notify_scheduled_job.sql +8 -0
  76. package/revert/schemas/app_jobs/triggers/tg_add_job_with_fields.sql +7 -0
  77. package/revert/schemas/app_jobs/triggers/tg_add_job_with_row.sql +7 -0
  78. package/revert/schemas/app_jobs/triggers/tg_add_job_with_row_id.sql +5 -0
  79. package/revert/schemas/app_jobs/triggers/tg_update_timestamps.sql +7 -0
  80. package/sql/launchql-jobs--0.4.6.sql +658 -0
  81. package/verify/schemas/app_jobs/helpers/json_build_object_apply.sql +7 -0
  82. package/verify/schemas/app_jobs/procedures/add_job.sql +7 -0
  83. package/verify/schemas/app_jobs/procedures/add_scheduled_job.sql +7 -0
  84. package/verify/schemas/app_jobs/procedures/complete_job.sql +7 -0
  85. package/verify/schemas/app_jobs/procedures/complete_jobs.sql +7 -0
  86. package/verify/schemas/app_jobs/procedures/do_notify.sql +7 -0
  87. package/verify/schemas/app_jobs/procedures/fail_job.sql +7 -0
  88. package/verify/schemas/app_jobs/procedures/get_job.sql +7 -0
  89. package/verify/schemas/app_jobs/procedures/get_scheduled_job.sql +7 -0
  90. package/verify/schemas/app_jobs/procedures/permanently_fail_jobs.sql +7 -0
  91. package/verify/schemas/app_jobs/procedures/release_jobs.sql +7 -0
  92. package/verify/schemas/app_jobs/procedures/release_scheduled_jobs.sql +7 -0
  93. package/verify/schemas/app_jobs/procedures/reschedule_jobs.sql +7 -0
  94. package/verify/schemas/app_jobs/procedures/run_scheduled_job.sql +7 -0
  95. package/verify/schemas/app_jobs/schema.sql +7 -0
  96. package/verify/schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator.sql +10 -0
  97. package/verify/schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx.sql +7 -0
  98. package/verify/schemas/app_jobs/tables/job_queues/table.sql +7 -0
  99. package/verify/schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_administrator.sql +10 -0
  100. package/verify/schemas/app_jobs/tables/jobs/indexes/jobs_locked_by_idx.sql +7 -0
  101. package/verify/schemas/app_jobs/tables/jobs/indexes/priority_run_at_id_idx.sql +7 -0
  102. package/verify/schemas/app_jobs/tables/jobs/table.sql +7 -0
  103. package/verify/schemas/app_jobs/tables/jobs/triggers/decrease_job_queue_count.sql +10 -0
  104. package/verify/schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count.sql +10 -0
  105. package/verify/schemas/app_jobs/tables/jobs/triggers/notify_worker.sql +6 -0
  106. package/verify/schemas/app_jobs/tables/jobs/triggers/timestamps.sql +16 -0
  107. package/verify/schemas/app_jobs/tables/scheduled_jobs/grants/grant_select_insert_update_delete_to_administrator.sql +10 -0
  108. package/verify/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_locked_by_idx.sql +7 -0
  109. package/verify/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_priority_id_idx.sql +7 -0
  110. package/verify/schemas/app_jobs/tables/scheduled_jobs/table.sql +7 -0
  111. package/verify/schemas/app_jobs/tables/scheduled_jobs/triggers/notify_scheduled_job.sql +8 -0
  112. package/verify/schemas/app_jobs/triggers/tg_add_job_with_fields.sql +7 -0
  113. package/verify/schemas/app_jobs/triggers/tg_add_job_with_row.sql +7 -0
  114. package/verify/schemas/app_jobs/triggers/tg_add_job_with_row_id.sql +6 -0
  115. package/verify/schemas/app_jobs/triggers/tg_update_timestamps.sql +7 -0
@@ -0,0 +1,26 @@
1
+ -- Deploy schemas/app_jobs/procedures/reschedule_jobs to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/jobs/table
4
+
5
+ BEGIN;
6
+ -- NOTE this should be renamed to reset_jobs to avoid confusion of scheduled jobs
7
+ CREATE FUNCTION app_jobs.reschedule_jobs (job_ids bigint[], run_at timestamptz DEFAULT NULL, priority integer DEFAULT NULL, attempts integer DEFAULT NULL, max_attempts integer DEFAULT NULL)
8
+ RETURNS SETOF app_jobs.jobs
9
+ LANGUAGE sql
10
+ AS $$
11
+ UPDATE
12
+ app_jobs.jobs
13
+ SET
14
+ run_at = coalesce(reschedule_jobs.run_at, jobs.run_at),
15
+ priority = coalesce(reschedule_jobs.priority, jobs.priority),
16
+ attempts = coalesce(reschedule_jobs.attempts, jobs.attempts),
17
+ max_attempts = coalesce(reschedule_jobs.max_attempts, jobs.max_attempts)
18
+ WHERE
19
+ id = ANY (job_ids)
20
+ AND (locked_by IS NULL
21
+ OR locked_at < NOW() - interval '4 hours')
22
+ RETURNING
23
+ *;
24
+ $$;
25
+ COMMIT;
26
+
@@ -0,0 +1,67 @@
1
+ -- Deploy schemas/app_jobs/procedures/run_scheduled_job to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/jobs/table
4
+ -- requires: schemas/app_jobs/tables/scheduled_jobs/table
5
+
6
+ BEGIN;
7
+ CREATE FUNCTION app_jobs.run_scheduled_job (id bigint, job_expiry interval DEFAULT '1 hours')
8
+ RETURNS app_jobs.jobs
9
+ AS $$
10
+ DECLARE
11
+ j app_jobs.jobs;
12
+ last_id bigint;
13
+ lkd_by text;
14
+ BEGIN
15
+ -- check last scheduled
16
+ SELECT
17
+ last_scheduled_id
18
+ FROM
19
+ app_jobs.scheduled_jobs s
20
+ WHERE
21
+ s.id = run_scheduled_job.id INTO last_id;
22
+ -- if it's been scheduled check if it's been run
23
+ IF (last_id IS NOT NULL) THEN
24
+ SELECT
25
+ locked_by
26
+ FROM
27
+ app_jobs.jobs js
28
+ WHERE
29
+ js.id = last_id
30
+ AND (js.locked_at IS NULL -- never been run
31
+ OR js.locked_at >= (NOW() - job_expiry)
32
+ -- still running within a safe interval
33
+ ) INTO lkd_by;
34
+ IF (FOUND) THEN
35
+ RAISE EXCEPTION 'ALREADY_SCHEDULED';
36
+ END IF;
37
+ END IF;
38
+ -- insert new job
39
+ INSERT INTO app_jobs.jobs (queue_name, task_identifier, payload, priority, max_attempts, key)
40
+ SELECT
41
+ queue_name,
42
+ task_identifier,
43
+ payload,
44
+ priority,
45
+ max_attempts,
46
+ key
47
+ FROM
48
+ app_jobs.scheduled_jobs s
49
+ WHERE
50
+ s.id = run_scheduled_job.id
51
+ RETURNING
52
+ * INTO j;
53
+ -- update the scheduled job
54
+ UPDATE
55
+ app_jobs.scheduled_jobs s
56
+ SET
57
+ last_scheduled = NOW(),
58
+ last_scheduled_id = j.id
59
+ WHERE
60
+ s.id = run_scheduled_job.id;
61
+ RETURN j;
62
+ END;
63
+ $$
64
+ LANGUAGE 'plpgsql'
65
+ VOLATILE;
66
+ COMMIT;
67
+
@@ -0,0 +1,7 @@
1
+ -- Deploy schemas/app_jobs/schema to pg
2
+ BEGIN;
3
+ CREATE SCHEMA IF NOT EXISTS app_jobs;
4
+ GRANT USAGE ON SCHEMA app_jobs TO administrator;
5
+ ALTER DEFAULT PRIVILEGES IN SCHEMA app_jobs GRANT EXECUTE ON FUNCTIONS TO administrator;
6
+ COMMIT;
7
+
@@ -0,0 +1,12 @@
1
+ -- Deploy schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator to pg
2
+
3
+ -- requires: schemas/app_jobs/schema
4
+ -- requires: schemas/app_jobs/tables/job_queues/table
5
+
6
+ BEGIN;
7
+
8
+ -- TODO make sure to require any policies on this table!
9
+
10
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE app_jobs.job_queues TO administrator;
11
+
12
+ COMMIT;
@@ -0,0 +1,8 @@
1
+ -- Deploy schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/job_queues/table
4
+
5
+ BEGIN;
6
+ CREATE INDEX job_queues_locked_by_idx ON app_jobs.job_queues (locked_by);
7
+ COMMIT;
8
+
@@ -0,0 +1,12 @@
1
+ -- Deploy schemas/app_jobs/tables/job_queues/table to pg
2
+ -- requires: schemas/app_jobs/schema
3
+
4
+ BEGIN;
5
+ CREATE TABLE app_jobs.job_queues (
6
+ queue_name text NOT NULL PRIMARY KEY,
7
+ job_count int DEFAULT 0 NOT NULL,
8
+ locked_at timestamptz,
9
+ locked_by text
10
+ );
11
+ COMMIT;
12
+
@@ -0,0 +1,12 @@
1
+ -- Deploy schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_administrator to pg
2
+
3
+ -- requires: schemas/app_jobs/schema
4
+ -- requires: schemas/app_jobs/tables/jobs/table
5
+
6
+ BEGIN;
7
+
8
+ -- TODO make sure to require any policies on this table!
9
+
10
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE app_jobs.jobs TO administrator;
11
+
12
+ COMMIT;
@@ -0,0 +1,8 @@
1
+ -- Deploy schemas/app_jobs/tables/jobs/indexes/jobs_locked_by_idx to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/jobs/table
4
+
5
+ BEGIN;
6
+ CREATE INDEX jobs_locked_by_idx ON app_jobs.jobs (locked_by);
7
+ COMMIT;
8
+
@@ -0,0 +1,8 @@
1
+ -- Deploy schemas/app_jobs/tables/jobs/indexes/priority_run_at_id_idx to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/jobs/table
4
+
5
+ BEGIN;
6
+ CREATE INDEX priority_run_at_id_idx ON app_jobs.jobs (priority, run_at, id);
7
+ COMMIT;
8
+
@@ -0,0 +1,26 @@
1
+ -- Deploy schemas/app_jobs/tables/jobs/table to pg
2
+ -- requires: schemas/app_jobs/schema
3
+
4
+ BEGIN;
5
+ CREATE TABLE app_jobs.jobs (
6
+ id bigserial PRIMARY KEY,
7
+ queue_name text DEFAULT (public.gen_random_uuid ()) ::text,
8
+ task_identifier text NOT NULL,
9
+ payload json DEFAULT '{}' ::json NOT NULL,
10
+ priority integer DEFAULT 0 NOT NULL,
11
+ run_at timestamptz DEFAULT now() NOT NULL,
12
+ attempts integer DEFAULT 0 NOT NULL,
13
+ max_attempts integer DEFAULT 25 NOT NULL,
14
+ key text,
15
+ last_error text,
16
+ locked_at timestamptz,
17
+ locked_by text,
18
+ CHECK (length(key) < 513),
19
+ CHECK (length(task_identifier) < 127),
20
+ CHECK (max_attempts > 0),
21
+ CHECK (length(queue_name) < 127),
22
+ CHECK (length(locked_by) > 3),
23
+ UNIQUE (key)
24
+ );
25
+ COMMIT;
26
+
@@ -0,0 +1,42 @@
1
+ -- Deploy schemas/app_jobs/tables/jobs/triggers/decrease_job_queue_count to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/jobs/table
4
+
5
+ BEGIN;
6
+ CREATE FUNCTION app_jobs.tg_decrease_job_queue_count ()
7
+ RETURNS TRIGGER
8
+ AS $$
9
+ DECLARE
10
+ v_new_job_count int;
11
+ BEGIN
12
+ UPDATE
13
+ app_jobs.job_queues
14
+ SET
15
+ job_count = job_queues.job_count - 1
16
+ WHERE
17
+ queue_name = OLD.queue_name
18
+ RETURNING
19
+ job_count INTO v_new_job_count;
20
+ IF v_new_job_count <= 0 THEN
21
+ DELETE FROM app_jobs.job_queues
22
+ WHERE queue_name = OLD.queue_name
23
+ AND job_count <= 0;
24
+ END IF;
25
+ RETURN OLD;
26
+ END;
27
+ $$
28
+ LANGUAGE 'plpgsql'
29
+ VOLATILE;
30
+ CREATE TRIGGER decrease_job_queue_count_on_delete
31
+ AFTER DELETE ON app_jobs.jobs
32
+ FOR EACH ROW
33
+ WHEN ((OLD.queue_name IS NOT NULL))
34
+ EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count ();
35
+ -- only a person would do this...
36
+ CREATE TRIGGER decrease_job_queue_count_on_update
37
+ AFTER UPDATE OF queue_name ON app_jobs.jobs
38
+ FOR EACH ROW
39
+ WHEN (((NEW.queue_name IS DISTINCT FROM OLD.queue_name) AND (OLD.queue_name IS NOT NULL)))
40
+ EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count ();
41
+ COMMIT;
42
+
@@ -0,0 +1,32 @@
1
+ -- Deploy schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/jobs/table
4
+
5
+ BEGIN;
6
+ CREATE FUNCTION app_jobs.tg_increase_job_queue_count ()
7
+ RETURNS TRIGGER
8
+ AS $$
9
+ BEGIN
10
+ INSERT INTO app_jobs.job_queues (queue_name, job_count)
11
+ VALUES (NEW.queue_name, 1)
12
+ ON CONFLICT (queue_name)
13
+ DO UPDATE SET
14
+ job_count = job_queues.job_count + 1;
15
+ RETURN NEW;
16
+ END;
17
+ $$
18
+ LANGUAGE 'plpgsql'
19
+ VOLATILE;
20
+ CREATE TRIGGER _500_increase_job_queue_count_on_insert
21
+ AFTER INSERT ON app_jobs.jobs
22
+ FOR EACH ROW
23
+ WHEN ((NEW.queue_name IS NOT NULL))
24
+ EXECUTE PROCEDURE app_jobs.tg_increase_job_queue_count ();
25
+ -- only a person would do this
26
+ CREATE TRIGGER _500_increase_job_queue_count_on_update
27
+ AFTER UPDATE OF queue_name ON app_jobs.jobs
28
+ FOR EACH ROW
29
+ WHEN (((NEW.queue_name IS DISTINCT FROM OLD.queue_name) AND (NEW.queue_name IS NOT NULL)))
30
+ EXECUTE PROCEDURE app_jobs.tg_increase_job_queue_count ();
31
+ COMMIT;
32
+
@@ -0,0 +1,13 @@
1
+ -- Deploy schemas/app_jobs/tables/jobs/triggers/notify_worker to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/jobs/table
4
+ -- requires: schemas/app_jobs/procedures/do_notify
5
+ -- requires: schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count
6
+
7
+ BEGIN;
8
+ CREATE TRIGGER _900_notify_worker
9
+ AFTER INSERT ON app_jobs.jobs
10
+ FOR EACH ROW
11
+ EXECUTE PROCEDURE app_jobs.do_notify ('jobs:insert');
12
+ COMMIT;
13
+
@@ -0,0 +1,20 @@
1
+ -- Deploy schemas/app_jobs/tables/jobs/triggers/timestamps to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/jobs/table
4
+ -- requires: schemas/app_jobs/triggers/tg_update_timestamps
5
+
6
+ BEGIN;
7
+ ALTER TABLE app_jobs.jobs
8
+ ADD COLUMN created_at timestamptz;
9
+ ALTER TABLE app_jobs.jobs
10
+ ALTER COLUMN created_at SET DEFAULT NOW();
11
+ ALTER TABLE app_jobs.jobs
12
+ ADD COLUMN updated_at timestamptz;
13
+ ALTER TABLE app_jobs.jobs
14
+ ALTER COLUMN updated_at SET DEFAULT NOW();
15
+ CREATE TRIGGER _100_update_jobs_modtime_tg
16
+ BEFORE UPDATE OR INSERT ON app_jobs.jobs
17
+ FOR EACH ROW
18
+ EXECUTE PROCEDURE app_jobs.tg_update_timestamps ();
19
+ COMMIT;
20
+
@@ -0,0 +1,12 @@
1
+ -- Deploy schemas/app_jobs/tables/scheduled_jobs/grants/grant_select_insert_update_delete_to_administrator to pg
2
+
3
+ -- requires: schemas/app_jobs/schema
4
+ -- requires: schemas/app_jobs/tables/scheduled_jobs/table
5
+
6
+ BEGIN;
7
+
8
+ -- TODO make sure to require any policies on this table!
9
+
10
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE app_jobs.scheduled_jobs TO administrator;
11
+
12
+ COMMIT;
@@ -0,0 +1,8 @@
1
+ -- Deploy schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_locked_by_idx to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/scheduled_jobs/table
4
+
5
+ BEGIN;
6
+ CREATE INDEX scheduled_jobs_locked_by_idx ON app_jobs.scheduled_jobs (locked_by);
7
+ COMMIT;
8
+
@@ -0,0 +1,8 @@
1
+ -- Deploy schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_priority_id_idx to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/scheduled_jobs/table
4
+
5
+ BEGIN;
6
+ CREATE INDEX scheduled_jobs_priority_id_idx ON app_jobs.scheduled_jobs (priority, id);
7
+ COMMIT;
8
+
@@ -0,0 +1,26 @@
1
+ -- Deploy schemas/app_jobs/tables/scheduled_jobs/table to pg
2
+ -- requires: schemas/app_jobs/schema
3
+
4
+ BEGIN;
5
+ CREATE TABLE app_jobs.scheduled_jobs (
6
+ id bigserial PRIMARY KEY,
7
+ queue_name text DEFAULT (public.gen_random_uuid ()) ::text,
8
+ task_identifier text NOT NULL,
9
+ payload json DEFAULT '{}' ::json NOT NULL,
10
+ priority integer DEFAULT 0 NOT NULL,
11
+ max_attempts integer DEFAULT 25 NOT NULL,
12
+ key text,
13
+ locked_at timestamptz,
14
+ locked_by text,
15
+ schedule_info json NOT NULL,
16
+ last_scheduled timestamptz,
17
+ last_scheduled_id bigint,
18
+ CHECK (length(key) < 513),
19
+ CHECK (length(task_identifier) < 127),
20
+ CHECK (max_attempts > 0),
21
+ CHECK (length(queue_name) < 127),
22
+ CHECK (length(locked_by) > 3),
23
+ UNIQUE (key)
24
+ );
25
+ COMMIT;
26
+
@@ -0,0 +1,12 @@
1
+ -- Deploy schemas/app_jobs/tables/scheduled_jobs/triggers/notify_scheduled_job to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/tables/scheduled_jobs/table
4
+ -- requires: schemas/app_jobs/procedures/do_notify
5
+
6
+ BEGIN;
7
+ CREATE TRIGGER _900_notify_scheduled_job
8
+ AFTER INSERT ON app_jobs.scheduled_jobs
9
+ FOR EACH ROW
10
+ EXECUTE PROCEDURE app_jobs.do_notify ('scheduled_jobs:insert');
11
+ COMMIT;
12
+
@@ -0,0 +1,50 @@
1
+ -- Deploy schemas/app_jobs/triggers/tg_add_job_with_fields to pg
2
+ -- requires: schemas/app_jobs/schema
3
+ -- requires: schemas/app_jobs/helpers/json_build_object_apply
4
+
5
+ BEGIN;
6
+ CREATE FUNCTION app_jobs.trigger_job_with_fields ()
7
+ RETURNS TRIGGER
8
+ AS $$
9
+ DECLARE
10
+ arg text;
11
+ fn text;
12
+ i int;
13
+ args text[];
14
+ BEGIN
15
+ FOR i IN
16
+ SELECT
17
+ *
18
+ FROM
19
+ generate_series(1, TG_NARGS) g (i)
20
+ LOOP
21
+ IF (i = 1) THEN
22
+ fn = TG_ARGV[i - 1];
23
+ ELSE
24
+ args = array_append(args, TG_ARGV[i - 1]);
25
+ IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
26
+ EXECUTE format('SELECT ($1).%s::text', TG_ARGV[i - 1])
27
+ USING NEW INTO arg;
28
+ END IF;
29
+ IF (TG_OP = 'DELETE') THEN
30
+ EXECUTE format('SELECT ($1).%s::text', TG_ARGV[i - 1])
31
+ USING OLD INTO arg;
32
+ END IF;
33
+ args = array_append(args, arg);
34
+ END IF;
35
+ END LOOP;
36
+ PERFORM
37
+ app_jobs.add_job (fn, app_jobs.json_build_object_apply (args));
38
+ IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
39
+ RETURN NEW;
40
+ END IF;
41
+ IF (TG_OP = 'DELETE') THEN
42
+ RETURN OLD;
43
+ END IF;
44
+ END;
45
+ $$
46
+ LANGUAGE plpgsql
47
+ VOLATILE
48
+ SECURITY DEFINER;
49
+ COMMIT;
50
+
@@ -0,0 +1,26 @@
1
+ -- Deploy schemas/app_jobs/triggers/tg_add_job_with_row to pg
2
+ -- requires: schemas/app_jobs/schema
3
+
4
+ BEGIN;
5
+ CREATE FUNCTION app_jobs.tg_add_job_with_row ()
6
+ RETURNS TRIGGER
7
+ AS $$
8
+ BEGIN
9
+ IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
10
+ PERFORM
11
+ app_jobs.add_job (TG_ARGV[0], to_json(NEW));
12
+ RETURN NEW;
13
+ END IF;
14
+ IF (TG_OP = 'DELETE') THEN
15
+ PERFORM
16
+ app_jobs.add_job (TG_ARGV[0], to_json(OLD));
17
+ RETURN OLD;
18
+ END IF;
19
+ END;
20
+ $$
21
+ LANGUAGE plpgsql
22
+ VOLATILE
23
+ SECURITY DEFINER;
24
+ COMMENT ON FUNCTION app_jobs.tg_add_job_with_row IS E'Useful shortcut to create a job on insert or update. Pass the task name as the trigger argument, and the record data will automatically be available on the JSON payload.';
25
+ COMMIT;
26
+
@@ -0,0 +1,26 @@
1
+ -- Deploy schemas/app_jobs/triggers/tg_add_job_with_row_id to pg
2
+ -- requires: schemas/app_jobs/schema
3
+
4
+ BEGIN;
5
+ CREATE FUNCTION app_jobs.tg_add_job_with_row_id ()
6
+ RETURNS TRIGGER
7
+ AS $$
8
+ BEGIN
9
+ IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
10
+ PERFORM
11
+ app_jobs.add_job (tg_argv[0], json_build_object('id', NEW.id));
12
+ RETURN NEW;
13
+ END IF;
14
+ IF (TG_OP = 'DELETE') THEN
15
+ PERFORM
16
+ app_jobs.add_job (tg_argv[0], json_build_object('id', OLD.id));
17
+ RETURN OLD;
18
+ END IF;
19
+ END;
20
+ $$
21
+ LANGUAGE plpgsql
22
+ VOLATILE
23
+ SECURITY DEFINER;
24
+ COMMENT ON FUNCTION app_jobs.tg_add_job_with_row_id IS E'Useful shortcut to create a job on insert or update. Pass the task name as the trigger argument, and the record id will automatically be available on the JSON payload.';
25
+ COMMIT;
26
+
@@ -0,0 +1,21 @@
1
+ -- Deploy schemas/app_jobs/triggers/tg_update_timestamps to pg
2
+ -- requires: schemas/app_jobs/schema
3
+
4
+ BEGIN;
5
+ CREATE FUNCTION app_jobs.tg_update_timestamps ()
6
+ RETURNS TRIGGER
7
+ AS $$
8
+ BEGIN
9
+ IF TG_OP = 'INSERT' THEN
10
+ NEW.created_at = NOW();
11
+ NEW.updated_at = NOW();
12
+ ELSIF TG_OP = 'UPDATE' THEN
13
+ NEW.created_at = OLD.created_at;
14
+ NEW.updated_at = greatest (now(), OLD.updated_at + interval '1 millisecond');
15
+ END IF;
16
+ RETURN NEW;
17
+ END;
18
+ $$
19
+ LANGUAGE 'plpgsql';
20
+ COMMIT;
21
+
package/jest.config.js ADDED
@@ -0,0 +1,15 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+
6
+ // Match both __tests__ and colocated test files
7
+ testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'],
8
+
9
+ // Ignore build artifacts and type declarations
10
+ testPathIgnorePatterns: ['/dist/', '\\.d\\.ts$'],
11
+ modulePathIgnorePatterns: ['<rootDir>/dist/'],
12
+ watchPathIgnorePatterns: ['/dist/'],
13
+
14
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
15
+ };
@@ -0,0 +1,8 @@
1
+ # launchql-jobs extension
2
+ comment = 'launchql-jobs extension'
3
+ default_version = '0.4.6'
4
+ module_pathname = '$libdir/launchql-jobs'
5
+ requires = 'plpgsql,uuid-ossp,pgcrypto,launchql-default-roles,launchql-verify'
6
+ relocatable = false
7
+ superuser = false
8
+
package/launchql.plan ADDED
@@ -0,0 +1,38 @@
1
+ %syntax-version=1.0.0
2
+ %project=launchql-jobs
3
+ %uri=launchql-jobs
4
+ schemas/app_jobs/schema [launchql-default-roles:@0.0.5 launchql-verify:@0.1.0] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/schema
5
+ schemas/app_jobs/triggers/tg_update_timestamps [schemas/app_jobs/schema] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/triggers/tg_update_timestamps
6
+ schemas/app_jobs/triggers/tg_add_job_with_row_id [schemas/app_jobs/schema] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/triggers/tg_add_job_with_row_id
7
+ schemas/app_jobs/triggers/tg_add_job_with_row [schemas/app_jobs/schema] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/triggers/tg_add_job_with_row
8
+ schemas/app_jobs/helpers/json_build_object_apply [schemas/app_jobs/schema] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/helpers/json_build_object_apply
9
+ schemas/app_jobs/triggers/tg_add_job_with_fields [schemas/app_jobs/schema schemas/app_jobs/helpers/json_build_object_apply] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/triggers/tg_add_job_with_fields
10
+ schemas/app_jobs/tables/scheduled_jobs/table [schemas/app_jobs/schema] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/scheduled_jobs/table
11
+ schemas/app_jobs/procedures/do_notify [schemas/app_jobs/schema] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/do_notify
12
+ schemas/app_jobs/tables/scheduled_jobs/triggers/notify_scheduled_job [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table schemas/app_jobs/procedures/do_notify] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/scheduled_jobs/triggers/notify_scheduled_job
13
+ schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_priority_id_idx [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_priority_id_idx
14
+ schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_locked_by_idx [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_locked_by_idx
15
+ schemas/app_jobs/tables/scheduled_jobs/grants/grant_select_insert_update_delete_to_administrator [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/scheduled_jobs/grants/grant_select_insert_update_delete_to_administrator
16
+ schemas/app_jobs/tables/jobs/table [schemas/app_jobs/schema] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/jobs/table
17
+ schemas/app_jobs/tables/jobs/triggers/timestamps [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/triggers/tg_update_timestamps] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/jobs/triggers/timestamps
18
+ schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count
19
+ schemas/app_jobs/tables/jobs/triggers/notify_worker [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/procedures/do_notify schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/jobs/triggers/notify_worker
20
+ schemas/app_jobs/tables/jobs/triggers/decrease_job_queue_count [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/jobs/triggers/decrease_job_queue_count
21
+ schemas/app_jobs/tables/jobs/indexes/priority_run_at_id_idx [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/jobs/indexes/priority_run_at_id_idx
22
+ schemas/app_jobs/tables/jobs/indexes/jobs_locked_by_idx [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/jobs/indexes/jobs_locked_by_idx
23
+ schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_administrator [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_administrator
24
+ schemas/app_jobs/tables/job_queues/table [schemas/app_jobs/schema] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/job_queues/table
25
+ schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx [schemas/app_jobs/schema schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx
26
+ schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator [schemas/app_jobs/schema schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator
27
+ schemas/app_jobs/procedures/run_scheduled_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/scheduled_jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/run_scheduled_job
28
+ schemas/app_jobs/procedures/reschedule_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/reschedule_jobs
29
+ schemas/app_jobs/procedures/release_scheduled_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/release_scheduled_jobs
30
+ schemas/app_jobs/procedures/release_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/release_jobs
31
+ schemas/app_jobs/procedures/permanently_fail_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/job_queues/table schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/permanently_fail_jobs
32
+ schemas/app_jobs/procedures/get_scheduled_job [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/get_scheduled_job
33
+ schemas/app_jobs/procedures/get_job [schemas/app_jobs/schema schemas/app_jobs/tables/job_queues/table schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/get_job
34
+ schemas/app_jobs/procedures/fail_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/fail_job
35
+ schemas/app_jobs/procedures/complete_jobs [schemas/app_jobs/schema schemas/app_jobs/tables/job_queues/table schemas/app_jobs/tables/jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/complete_jobs
36
+ schemas/app_jobs/procedures/complete_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/complete_job
37
+ schemas/app_jobs/procedures/add_scheduled_job [schemas/app_jobs/schema schemas/app_jobs/tables/scheduled_jobs/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/add_scheduled_job
38
+ schemas/app_jobs/procedures/add_job [schemas/app_jobs/schema schemas/app_jobs/tables/jobs/table schemas/app_jobs/tables/job_queues/table] 2025-08-26T23:57:44Z launchql <launchql@5b0c196eeb62> # add schemas/app_jobs/procedures/add_job
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@pgpm/jobs",
3
+ "version": "0.4.0",
4
+ "description": "Core job system for background task processing in PostgreSQL",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "scripts": {
9
+ "bundle": "lql package",
10
+ "test": "jest",
11
+ "test:watch": "jest --watch"
12
+ },
13
+ "dependencies": {
14
+ "@pgpm/default-roles": "0.4.0",
15
+ "@pgpm/verify": "0.4.0"
16
+ },
17
+ "devDependencies": {
18
+ "@launchql/cli": "^4.9.0"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/launchql/extensions"
23
+ },
24
+ "homepage": "https://github.com/launchql/extensions",
25
+ "bugs": {
26
+ "url": "https://github.com/launchql/extensions/issues"
27
+ },
28
+ "gitHead": "cc9f52a335caa6e21ee7751b04b77c84ce6cb809"
29
+ }
@@ -0,0 +1,7 @@
1
+ -- Revert schemas/app_jobs/helpers/json_build_object_apply from pg
2
+
3
+ BEGIN;
4
+
5
+ DROP FUNCTION app_jobs.json_build_object_apply;
6
+
7
+ COMMIT;
@@ -0,0 +1,7 @@
1
+ -- Revert schemas/app_jobs/procedures/add_job from pg
2
+
3
+ BEGIN;
4
+
5
+ DROP FUNCTION app_jobs.add_job;
6
+
7
+ COMMIT;
@@ -0,0 +1,7 @@
1
+ -- Revert schemas/app_jobs/procedures/add_scheduled_job from pg
2
+
3
+ BEGIN;
4
+
5
+ DROP FUNCTION app_jobs.add_scheduled_job;
6
+
7
+ COMMIT;
@@ -0,0 +1,7 @@
1
+ -- Revert schemas/app_jobs/procedures/complete_job from pg
2
+
3
+ BEGIN;
4
+
5
+ DROP FUNCTION app_jobs.complete_job;
6
+
7
+ COMMIT;