letopis 0.21.0 → 1.0.1
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/AGENT-CHEATSHEET.en.md +368 -0
- package/AGENT-CHEATSHEET.md +354 -0
- package/CHANGELOG.md +337 -0
- package/MIGRATION.md +190 -0
- package/README.en.md +1938 -0
- package/README.md +1493 -3469
- package/dist/acl.d.ts +26 -50
- package/dist/acl.js +22 -267
- package/dist/admin.d.ts +138 -0
- package/dist/admin.js +170 -0
- package/dist/auth.d.ts +120 -73
- package/dist/auth.js +121 -306
- package/dist/cache.d.ts +73 -0
- package/dist/cache.js +148 -0
- package/dist/chain.d.ts +124 -191
- package/dist/chain.js +362 -563
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +178 -0
- package/dist/demo/booking.d.ts +289 -0
- package/dist/demo/booking.js +159 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +70 -0
- package/dist/import.d.ts +179 -0
- package/dist/import.js +792 -0
- package/dist/index.d.ts +172 -26
- package/dist/index.js +304 -178
- package/dist/jsonschema.d.ts +22 -0
- package/dist/jsonschema.js +167 -0
- package/dist/load.d.ts +76 -0
- package/dist/load.js +884 -0
- package/dist/model.d.ts +166 -0
- package/dist/model.js +224 -0
- package/dist/ops.d.ts +7 -6
- package/dist/ops.js +7 -51
- package/dist/pglite.d.ts +22 -0
- package/dist/pglite.js +45 -0
- package/dist/registry.d.ts +57 -0
- package/dist/registry.js +82 -0
- package/dist/sql.d.ts +59 -142
- package/dist/sql.js +568 -654
- package/dist/sync.d.ts +31 -0
- package/dist/sync.js +108 -0
- package/dist/tx.d.ts +129 -8
- package/dist/tx.js +300 -73
- package/dist/typed.d.ts +97 -0
- package/dist/typed.js +1 -0
- package/dist/types.d.ts +71 -252
- package/dist/types.js +27 -108
- package/dist/up.d.ts +140 -47
- package/dist/up.js +339 -267
- package/dist/uuid.d.ts +21 -6
- package/dist/uuid.js +48 -64
- package/dist/validate.d.ts +24 -0
- package/dist/validate.js +251 -0
- package/dist/watch.d.ts +62 -0
- package/dist/watch.js +168 -0
- package/dist/write.d.ts +117 -74
- package/dist/write.js +658 -720
- package/llms.txt +26 -0
- package/package.json +49 -19
- package/sql/10-core.sql +136 -0
- package/sql/15-errors.sql +60 -0
- package/sql/20-context.sql +153 -0
- package/sql/30-validate.sql +423 -0
- package/sql/40-class.sql +259 -0
- package/sql/50-acl.sql +539 -0
- package/sql/60-write.sql +1372 -0
- package/sql/70-read.sql +245 -0
- package/sql/80-auth.sql +827 -0
- package/sql/90-time.sql +957 -0
- package/sql/95-seed.system.sql +178 -0
- package/sql/99-revision.sql +3 -0
- package/sql/README.md +56 -0
- package/sql/seed.booking.sql +39 -112
- package/dist/schema.d.ts +0 -15
- package/dist/schema.js +0 -352
- package/dist/sessions.d.ts +0 -32
- package/dist/sessions.js +0 -114
- package/dist/tables.d.ts +0 -105
- package/dist/tables.js +0 -248
- package/docker/Dockerfile +0 -40
- package/docker/start.sh +0 -18
- package/scripts/check-docs.mjs +0 -375
- package/scripts/gen-api-contract.mjs +0 -226
- package/scripts/gen-types.mjs +0 -350
- package/scripts/release-notes.mjs +0 -76
- package/scripts/schema-sync.mjs +0 -185
- package/sql/ddl.sql +0 -685
- package/sql/seed.auth.sql +0 -73
package/dist/tables.js
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { reservedNamesOf } from './types.js';
|
|
2
|
-
const T = (ctx, name) => `"${ctx.pgSchema.replace(/"/g, '""')}"."${name}"`;
|
|
3
|
-
// START_CONTRACT: where
|
|
4
|
-
// PURPOSE: Собрать SQL WHERE из простых equality-условий, добавляя значения в params.
|
|
5
|
-
// INPUTS: { conds: Cond[] - условия с генераторами текста; params: unknown[] - аккумулятор значений }
|
|
6
|
-
// OUTPUTS: { string - " WHERE ..." или пустая строка }
|
|
7
|
-
// SIDE_EFFECTS: пушит значения в params (мутация массива)
|
|
8
|
-
// LINKS: M-TABLES, V-M-TABLES
|
|
9
|
-
// END_CONTRACT: where
|
|
10
|
-
/** WHERE из простых equality-условий (+спец-обработчики). */
|
|
11
|
-
function where(conds, params) {
|
|
12
|
-
if (!conds.length)
|
|
13
|
-
return '';
|
|
14
|
-
const parts = conds.map((c) => {
|
|
15
|
-
params.push(c.value);
|
|
16
|
-
return c.text(params.length);
|
|
17
|
-
});
|
|
18
|
-
return ' WHERE ' + parts.join(' AND ');
|
|
19
|
-
}
|
|
20
|
-
const eq = (col) => (n) => `"${col}" = $${n}`;
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// START_CONTRACT: makeTables
|
|
23
|
-
// PURPOSE: Построить фасад Tables (accounts/credentials/resources/rules) поверх Ctx.
|
|
24
|
-
// INPUTS: { ctx: Ctx - runtime-контекст с sql и pgSchema }
|
|
25
|
-
// OUTPUTS: { Tables - четыре API с find/get/set/delete }
|
|
26
|
-
// SIDE_EFFECTS: каждый метод выполняет SELECT/INSERT/UPDATE/DELETE через ctx.sql
|
|
27
|
-
// LINKS: M-TABLES, V-M-TABLES, M-DDL
|
|
28
|
-
// END_CONTRACT: makeTables
|
|
29
|
-
export function makeTables(ctx) {
|
|
30
|
-
const run = (text, params) => ctx.sql.unsafe(text, params);
|
|
31
|
-
// START_BLOCK_ACCOUNTS_API
|
|
32
|
-
const accounts = {
|
|
33
|
-
async find(f = {}) {
|
|
34
|
-
const conds = [];
|
|
35
|
-
if (f.id !== undefined)
|
|
36
|
-
conds.push({ text: eq('id'), value: f.id });
|
|
37
|
-
if (f.enabled !== undefined)
|
|
38
|
-
conds.push({ text: eq('enabled'), value: f.enabled });
|
|
39
|
-
if (f.category !== undefined)
|
|
40
|
-
conds.push({ text: (n) => `$${n} = ANY(categories)`, value: f.category });
|
|
41
|
-
const params = [];
|
|
42
|
-
return run(`SELECT * FROM ${T(ctx, 'Account')}${where(conds, params)} ORDER BY created`, params);
|
|
43
|
-
},
|
|
44
|
-
async get(id) {
|
|
45
|
-
return (await run(`SELECT * FROM ${T(ctx, 'Account')} WHERE id = $1`, [id]))[0] ?? null;
|
|
46
|
-
},
|
|
47
|
-
// START_CONTRACT: accounts.set
|
|
48
|
-
// PURPOSE: INSERT (без id) либо UPDATE по id аккаунта; вернуть строку Account.
|
|
49
|
-
// INPUTS: { a: Partial<Account> - поля categories/data/meta/avatar/enabled (+id для update) }
|
|
50
|
-
// OUTPUTS: { Promise<Account> - созданная/обновлённая строка }
|
|
51
|
-
// SIDE_EFFECTS: INSERT или UPDATE в таблице Account
|
|
52
|
-
// ERRORS: account "<id>" not found (UPDATE не нашёл строку)
|
|
53
|
-
// LINKS: M-TABLES, V-M-TABLES
|
|
54
|
-
// END_CONTRACT: accounts.set
|
|
55
|
-
async set(a) {
|
|
56
|
-
if (a.id) {
|
|
57
|
-
const sets = ['updated = now()'];
|
|
58
|
-
const params = [];
|
|
59
|
-
for (const col of ['categories', 'data', 'meta', 'avatar', 'enabled']) {
|
|
60
|
-
if (a[col] !== undefined) {
|
|
61
|
-
params.push(a[col]);
|
|
62
|
-
sets.push(`"${col}" = $${params.length}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
params.push(a.id);
|
|
66
|
-
const rows = await run(`UPDATE ${T(ctx, 'Account')} SET ${sets.join(', ')} WHERE id = $${params.length} RETURNING *`, params);
|
|
67
|
-
if (!rows[0])
|
|
68
|
-
throw new Error(`letopis: account "${a.id}" not found`);
|
|
69
|
-
return rows[0];
|
|
70
|
-
}
|
|
71
|
-
const cols = [];
|
|
72
|
-
const params = [];
|
|
73
|
-
for (const col of ['categories', 'data', 'meta', 'avatar', 'enabled']) {
|
|
74
|
-
if (a[col] !== undefined) {
|
|
75
|
-
params.push(a[col]);
|
|
76
|
-
cols.push(`"${col}"`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
const ph = params.map((_, i) => `$${i + 1}`).join(', ');
|
|
80
|
-
const rows = await run(cols.length
|
|
81
|
-
? `INSERT INTO ${T(ctx, 'Account')} (${cols.join(', ')}) VALUES (${ph}) RETURNING *`
|
|
82
|
-
: `INSERT INTO ${T(ctx, 'Account')} DEFAULT VALUES RETURNING *`, params);
|
|
83
|
-
return rows[0];
|
|
84
|
-
},
|
|
85
|
-
async delete(id) {
|
|
86
|
-
const rows = await run(`DELETE FROM ${T(ctx, 'Account')} WHERE id = $1 RETURNING id`, [id]);
|
|
87
|
-
return rows.length > 0;
|
|
88
|
-
},
|
|
89
|
-
// START_CONTRACT: accounts.purge
|
|
90
|
-
// PURPOSE: Физический офбординг тенанта через purge_account(); гарды Owner/System, последний Owner, не-себя — до сноса, в одной tx.
|
|
91
|
-
// INPUTS: { id: string - целевой аккаунт }
|
|
92
|
-
// OUTPUTS: { Promise<boolean> - true если Account физически снесён }
|
|
93
|
-
// SIDE_EFFECTS: физ. DELETE всех Entity аккаунта + Account (Credential FK-каскад) через SQL purge_account()
|
|
94
|
-
// ERRORS: нет caller/ACL; вызов не Owner/System; снос последнего Owner; снос своего аккаунта
|
|
95
|
-
// LINKS: M-TABLES, V-M-TABLES, M-DDL
|
|
96
|
-
// END_CONTRACT: accounts.purge
|
|
97
|
-
async purge(id) {
|
|
98
|
-
if (!ctx.account) {
|
|
99
|
-
throw new Error('letopis: accounts.purge requires an authenticated caller — call it from a scoped handle: ' +
|
|
100
|
-
'(await db.as(caller)).accounts.purge(id)');
|
|
101
|
-
}
|
|
102
|
-
if (id === ctx.account) {
|
|
103
|
-
throw new Error('letopis: accounts.purge cannot purge the calling account itself');
|
|
104
|
-
}
|
|
105
|
-
const schema = `"${ctx.pgSchema.replace(/"/g, '""')}"`;
|
|
106
|
-
return ctx.sql.begin(async (tsql) => {
|
|
107
|
-
const q = (t, p) => tsql.unsafe(t, p);
|
|
108
|
-
// вызывающий обязан быть Owner/System
|
|
109
|
-
const caller = await q(`SELECT categories FROM ${T(ctx, 'Account')} WHERE id = $1`, [ctx.account]);
|
|
110
|
-
const cats = caller[0]?.categories ?? [];
|
|
111
|
-
if (!cats.includes('Owner') && !cats.includes('System')) {
|
|
112
|
-
throw new Error('letopis: accounts.purge is restricted to Owner/System accounts');
|
|
113
|
-
}
|
|
114
|
-
const tgt = await q(`SELECT categories FROM ${T(ctx, 'Account')} WHERE id = $1`, [id]);
|
|
115
|
-
if (!tgt.length)
|
|
116
|
-
return false;
|
|
117
|
-
// нельзя снести последний enabled Owner → тенант без владельца (лок-аут)
|
|
118
|
-
if ((tgt[0].categories ?? []).includes('Owner')) {
|
|
119
|
-
const cnt = await q(`SELECT count(*)::text AS n FROM ${T(ctx, 'Account')} WHERE 'Owner' = ANY(categories) AND enabled`, []);
|
|
120
|
-
if (Number(cnt[0]?.n ?? 0) <= 1) {
|
|
121
|
-
throw new Error('letopis: refusing to purge the last enabled Owner (tenant lock-out)');
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
const res = await q(`SELECT ${schema}.purge_account($1::uuid) AS id`, [id]);
|
|
125
|
-
return res[0]?.id != null;
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
// END_BLOCK_ACCOUNTS_API
|
|
130
|
-
// START_BLOCK_CREDENTIALS_API
|
|
131
|
-
const credentials = {
|
|
132
|
-
async find(f = {}) {
|
|
133
|
-
const conds = [];
|
|
134
|
-
if (f.id !== undefined)
|
|
135
|
-
conds.push({ text: eq('id'), value: f.id });
|
|
136
|
-
if (f.account !== undefined)
|
|
137
|
-
conds.push({ text: eq('account'), value: f.account });
|
|
138
|
-
if (f.category !== undefined)
|
|
139
|
-
conds.push({ text: eq('category'), value: f.category });
|
|
140
|
-
if (f.identifier !== undefined)
|
|
141
|
-
conds.push({ text: eq('identifier'), value: f.identifier });
|
|
142
|
-
if (f.confirmed !== undefined)
|
|
143
|
-
conds.push({ text: eq('confirmed'), value: f.confirmed });
|
|
144
|
-
const params = [];
|
|
145
|
-
let sql = `SELECT * FROM ${T(ctx, 'Credential')}${where(conds, params)}`;
|
|
146
|
-
if (!f.withDeleted)
|
|
147
|
-
sql += conds.length ? ' AND deleted IS NULL' : ' WHERE deleted IS NULL';
|
|
148
|
-
return run(sql + ' ORDER BY created', params);
|
|
149
|
-
},
|
|
150
|
-
// START_CONTRACT: credentials.set
|
|
151
|
-
// PURPOSE: upsert креденшела по UNIQUE (account, category, identifier); повторный set воскрешает (deleted → NULL).
|
|
152
|
-
// INPUTS: { c: { account, category, identifier, meta?, confirmed? } }
|
|
153
|
-
// OUTPUTS: { Promise<Credential> - upsert-нутая строка }
|
|
154
|
-
// SIDE_EFFECTS: INSERT ... ON CONFLICT DO UPDATE в таблице Credential
|
|
155
|
-
// LINKS: M-TABLES, V-M-TABLES
|
|
156
|
-
// END_CONTRACT: credentials.set
|
|
157
|
-
async set(c) {
|
|
158
|
-
const rows = await run(`INSERT INTO ${T(ctx, 'Credential')} (account, category, identifier, meta, confirmed)
|
|
159
|
-
VALUES ($1, $2, $3, $4, $5)
|
|
160
|
-
ON CONFLICT (account, category, identifier) DO UPDATE
|
|
161
|
-
SET meta = EXCLUDED.meta, confirmed = EXCLUDED.confirmed, updated = now(), deleted = NULL
|
|
162
|
-
RETURNING *`, [c.account, c.category, c.identifier, c.meta ?? {}, c.confirmed ?? true]);
|
|
163
|
-
return rows[0];
|
|
164
|
-
},
|
|
165
|
-
async delete(id) {
|
|
166
|
-
const rows = await run(`UPDATE ${T(ctx, 'Credential')} SET deleted = now(), updated = now() WHERE id = $1 AND deleted IS NULL RETURNING id`, [id]);
|
|
167
|
-
return rows.length > 0;
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
// END_BLOCK_CREDENTIALS_API
|
|
171
|
-
// START_BLOCK_RESOURCES_API
|
|
172
|
-
const resources = {
|
|
173
|
-
async find(f = {}) {
|
|
174
|
-
const conds = [];
|
|
175
|
-
if (f.category !== undefined)
|
|
176
|
-
conds.push({ text: eq('category'), value: f.category });
|
|
177
|
-
const params = [];
|
|
178
|
-
return run(`SELECT * FROM ${T(ctx, 'Resource')}${where(conds, params)} ORDER BY alias`, params);
|
|
179
|
-
},
|
|
180
|
-
async get(alias) {
|
|
181
|
-
return (await run(`SELECT * FROM ${T(ctx, 'Resource')} WHERE alias = $1`, [alias]))[0] ?? null;
|
|
182
|
-
},
|
|
183
|
-
async set(r) {
|
|
184
|
-
const rows = await run(`INSERT INTO ${T(ctx, 'Resource')} (alias, category, pattern, meta) VALUES ($1, $2, $3, $4)
|
|
185
|
-
ON CONFLICT (alias) DO UPDATE SET category = EXCLUDED.category, pattern = EXCLUDED.pattern, meta = EXCLUDED.meta
|
|
186
|
-
RETURNING *`, [r.alias, r.category, r.pattern ?? null, r.meta ?? null]);
|
|
187
|
-
return rows[0];
|
|
188
|
-
},
|
|
189
|
-
async delete(alias) {
|
|
190
|
-
const rows = await run(`DELETE FROM ${T(ctx, 'Resource')} WHERE alias = $1 RETURNING alias`, [alias]);
|
|
191
|
-
return rows.length > 0;
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
|
-
// END_BLOCK_RESOURCES_API
|
|
195
|
-
// START_BLOCK_RULES_API
|
|
196
|
-
const rules = {
|
|
197
|
-
async find(f = {}) {
|
|
198
|
-
const conds = [];
|
|
199
|
-
if (f.account !== undefined)
|
|
200
|
-
conds.push({ text: eq('account'), value: f.account });
|
|
201
|
-
if (f.resource !== undefined)
|
|
202
|
-
conds.push({ text: eq('resource'), value: f.resource });
|
|
203
|
-
if (f.permission !== undefined)
|
|
204
|
-
conds.push({ text: eq('permission'), value: f.permission });
|
|
205
|
-
if (f.enabled !== undefined)
|
|
206
|
-
conds.push({ text: eq('enabled'), value: f.enabled });
|
|
207
|
-
const params = [];
|
|
208
|
-
return run(`SELECT * FROM ${T(ctx, 'Rule')}${where(conds, params)} ORDER BY weight DESC NULLS LAST`, params);
|
|
209
|
-
},
|
|
210
|
-
async set(r) {
|
|
211
|
-
const rows = await run(`INSERT INTO ${T(ctx, 'Rule')} (account, resource, permission, weight, meta, enabled)
|
|
212
|
-
VALUES ($1, $2, $3, $4, $5, $6)
|
|
213
|
-
ON CONFLICT (account, resource) DO UPDATE
|
|
214
|
-
SET permission = EXCLUDED.permission, weight = EXCLUDED.weight, meta = EXCLUDED.meta, enabled = EXCLUDED.enabled
|
|
215
|
-
RETURNING *`, [r.account, r.resource, r.permission, r.weight ?? null, r.meta ?? null, r.enabled ?? true]);
|
|
216
|
-
return rows[0];
|
|
217
|
-
},
|
|
218
|
-
async delete(account, resource) {
|
|
219
|
-
const rows = await run(`DELETE FROM ${T(ctx, 'Rule')} WHERE account = $1 AND resource = $2 RETURNING account`, [account, resource]);
|
|
220
|
-
return rows.length > 0;
|
|
221
|
-
},
|
|
222
|
-
};
|
|
223
|
-
// END_BLOCK_RULES_API
|
|
224
|
-
// START_BLOCK_SCHEMA_API
|
|
225
|
-
const schema = {
|
|
226
|
-
async define(def) {
|
|
227
|
-
// класс с именем, которое Proxy разбирает до резолва класса, был бы недостижим как шаг
|
|
228
|
-
const clash = reservedNamesOf(def);
|
|
229
|
-
if (clash.length) {
|
|
230
|
-
throw new Error(`letopis: class name ${clash.map((n) => `"${n}"`).join(' / ')} is reserved by the chain API ` +
|
|
231
|
-
`(a step under that name resolves to the method, not the class) — rename the ${clash.includes(def.id) ? 'id' : 'alias'}`);
|
|
232
|
-
}
|
|
233
|
-
// jsonb-значения через sql.json() — postgres.js кладёт как jsonb (не PG-массив/строку)
|
|
234
|
-
const j = (v) => ctx.sql.json(v);
|
|
235
|
-
await run(`INSERT INTO ${T(ctx, 'Schema')} (partition, id, alias, category, ancestor, attributes, meta, links, "order")
|
|
236
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
237
|
-
ON CONFLICT (partition, id) DO UPDATE SET
|
|
238
|
-
alias = EXCLUDED.alias, category = EXCLUDED.category, ancestor = EXCLUDED.ancestor,
|
|
239
|
-
attributes = EXCLUDED.attributes, meta = EXCLUDED.meta, links = EXCLUDED.links, "order" = EXCLUDED."order"`, [
|
|
240
|
-
ctx.partition, def.id, def.alias, def.category, def.ancestor ?? null,
|
|
241
|
-
j(def.attributes ?? {}), j(def.meta ?? {}), j(def.links ?? []), def.order ?? 0,
|
|
242
|
-
]);
|
|
243
|
-
await ctx.reload?.(); // подхватить новое определение сразу (registry + enforcer)
|
|
244
|
-
},
|
|
245
|
-
};
|
|
246
|
-
// END_BLOCK_SCHEMA_API
|
|
247
|
-
return { accounts, credentials, resources, rules, schema };
|
|
248
|
-
}
|
package/docker/Dockerfile
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Dev-контейнер letopis: TimescaleDB + Redis в одном контейнере (решение проекта).
|
|
2
|
-
# Обычный путь — letopis.up() собирает и запускает сам. Руками:
|
|
3
|
-
# docker build -t letopis-db lib/docker
|
|
4
|
-
# docker run -d --name letopis-timescale -p 15432:5432 -p 16379:6379 \
|
|
5
|
-
# -v letopis-pgdata:/var/lib/postgresql/data \
|
|
6
|
-
# -e POSTGRES_PASSWORD=test -e POSTGRES_DB=letopis letopis-db
|
|
7
|
-
# FILE: lib/docker/Dockerfile
|
|
8
|
-
# VERSION: 1.0.0
|
|
9
|
-
# START_MODULE_CONTRACT
|
|
10
|
-
# PURPOSE: Dev-образ БД — TimescaleDB pg17 + Redis в одном контейнере (для letopis.up()).
|
|
11
|
-
# SCOPE: базовый образ, установка redis, копирование start.sh, EXPOSE 5432/6379, entrypoint.
|
|
12
|
-
# DEPENDS: none
|
|
13
|
-
# LINKS: M-DOCKER, V-M-DOCKER
|
|
14
|
-
# ROLE: CONFIG
|
|
15
|
-
# MAP_MODE: SUMMARY
|
|
16
|
-
# END_MODULE_CONTRACT
|
|
17
|
-
#
|
|
18
|
-
# START_MODULE_MAP
|
|
19
|
-
# FROM timescale/timescaledb:latest-pg17 - база (PostgreSQL 17 + TimescaleDB)
|
|
20
|
-
# apk add redis - Redis в тот же образ
|
|
21
|
-
# ENTRYPOINT start-letopis.sh - запуск redis + postgres
|
|
22
|
-
# END_MODULE_MAP
|
|
23
|
-
#
|
|
24
|
-
# START_CHANGE_SUMMARY
|
|
25
|
-
# LAST_CHANGE: [v1.0.0 - Documented existing module: reverse-engineered contract + markup]
|
|
26
|
-
# END_CHANGE_SUMMARY
|
|
27
|
-
|
|
28
|
-
# START_BLOCK_IMAGE_BUILD
|
|
29
|
-
FROM timescale/timescaledb:latest-pg17
|
|
30
|
-
|
|
31
|
-
RUN apk add --no-cache redis
|
|
32
|
-
|
|
33
|
-
COPY start.sh /usr/local/bin/start-letopis.sh
|
|
34
|
-
RUN chmod +x /usr/local/bin/start-letopis.sh
|
|
35
|
-
|
|
36
|
-
EXPOSE 5432 6379
|
|
37
|
-
|
|
38
|
-
ENTRYPOINT ["/usr/local/bin/start-letopis.sh"]
|
|
39
|
-
CMD ["postgres"]
|
|
40
|
-
# END_BLOCK_IMAGE_BUILD
|
package/docker/start.sh
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# FILE: lib/docker/start.sh
|
|
3
|
-
# VERSION: 1.0.0
|
|
4
|
-
# START_MODULE_CONTRACT
|
|
5
|
-
# PURPOSE: Entrypoint dev-контейнера — поднять Redis фоном, затем штатный postgres docker-entrypoint.
|
|
6
|
-
# SCOPE: redis-server (daemonize) + exec docker-entrypoint.sh.
|
|
7
|
-
# DEPENDS: none
|
|
8
|
-
# LINKS: M-DOCKER, V-M-DOCKER
|
|
9
|
-
# ROLE: SCRIPT
|
|
10
|
-
# MAP_MODE: NONE
|
|
11
|
-
# END_MODULE_CONTRACT
|
|
12
|
-
#
|
|
13
|
-
# START_CHANGE_SUMMARY
|
|
14
|
-
# LAST_CHANGE: [v1.0.0 - Documented existing module: reverse-engineered contract + markup]
|
|
15
|
-
# END_CHANGE_SUMMARY
|
|
16
|
-
# Redis фоном (dev: без пароля и персиста — сессии эфемерны), затем штатный entrypoint postgres.
|
|
17
|
-
redis-server --port 6379 --bind 0.0.0.0 --protected-mode no --save '' --appendonly no --daemonize yes
|
|
18
|
-
exec docker-entrypoint.sh "$@"
|