@tstdl/base 0.93.149 → 0.93.151

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.
@@ -1,4 +1,5 @@
1
1
  import type { Part } from 'genkit';
2
+ import type { SchemaTestable } from '../../schema/schema.js';
2
3
  import type { ObjectLiteral } from '../../types/index.js';
3
4
  import { type Instructions } from './instructions-formatter.js';
4
5
  export type PromptBuilderInstructions = Record<string, Instructions>;
@@ -10,6 +11,8 @@ export declare class PromptBuilder {
10
11
  setRole(role: string): this;
11
12
  setSystemTask(task: string): this;
12
13
  setTask(task: string): this;
14
+ setSystemOutputSchema(schema: SchemaTestable): this;
15
+ setOutputSchema(schema: SchemaTestable): this;
13
16
  addSystemMedia(content: Uint8Array, mimeType: string): this;
14
17
  addMedia(content: Uint8Array, mimeType: string): this;
15
18
  addSystemInstructions(instructions: Record<string, Instructions>): this;
@@ -1,3 +1,4 @@
1
+ import { convertToOpenApiSchema } from '../../schema/converters/open-api-converter.js';
1
2
  import { encodeBase64 } from '../../utils/base64.js';
2
3
  import { fromEntries, objectEntries, objectKeys } from '../../utils/object/index.js';
3
4
  import { assertObjectPass, isDefined, isString, isUndefined } from '../../utils/type-guards.js';
@@ -10,6 +11,8 @@ export class PromptBuilder {
10
11
  #role;
11
12
  #systemTask;
12
13
  #task;
14
+ #systemOutputSchema;
15
+ #outputSchema;
13
16
  #systemInstructions = {};
14
17
  #instructions = {};
15
18
  #systemContextParts = {};
@@ -30,6 +33,14 @@ export class PromptBuilder {
30
33
  this.#task = task;
31
34
  return this;
32
35
  }
36
+ setSystemOutputSchema(schema) {
37
+ this.#systemOutputSchema = schema;
38
+ return this;
39
+ }
40
+ setOutputSchema(schema) {
41
+ this.#outputSchema = schema;
42
+ return this;
43
+ }
33
44
  addSystemMedia(content, mimeType) {
34
45
  addMedia(content, mimeType, this.#systemMedia);
35
46
  return this;
@@ -67,6 +78,7 @@ export class PromptBuilder {
67
78
  role: this.#systemRole,
68
79
  context: this.#systemContextParts,
69
80
  instructions: this.#systemInstructions,
81
+ outputSchema: this.#systemOutputSchema,
70
82
  task: this.#systemTask,
71
83
  media: this.#systemMedia,
72
84
  });
@@ -76,6 +88,7 @@ export class PromptBuilder {
76
88
  role: this.#role,
77
89
  context: this.#contextParts,
78
90
  instructions: this.#instructions,
91
+ outputSchema: this.#outputSchema,
79
92
  task: this.#task,
80
93
  media: this.#media,
81
94
  });
@@ -104,6 +117,10 @@ function buildPrompt(data) {
104
117
  if (isDefined(data.instructions) && (objectKeys(data.instructions).length > 0)) {
105
118
  instructions['**Instructions**'] = data.instructions;
106
119
  }
120
+ if (isDefined(data.outputSchema)) {
121
+ const schema = convertToOpenApiSchema(data.outputSchema);
122
+ instructions['**Output Schema**'] = `\`\`\`json\n${JSON.stringify(schema, null, 2)}\n\`\`\``;
123
+ }
107
124
  if (isDefined(data.task)) {
108
125
  instructions['**Task**'] = data.task;
109
126
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { StringSchema } from '../../../schema/schemas/string.js';
3
+ import { promptBuilder } from '../prompt-builder.js';
4
+ describe('PromptBuilder', () => {
5
+ it('should add output schema to the prompt', () => {
6
+ const builder = promptBuilder()
7
+ .setRole('Test Role')
8
+ .setOutputSchema(new StringSchema({ description: 'Test Description' }))
9
+ .setTask('Test Task');
10
+ const prompt = builder.buildUserPrompt();
11
+ const text = prompt[0].text;
12
+ expect(text).toContain('# **Output Schema**');
13
+ expect(text).toContain('"type": "string"');
14
+ expect(text).toContain('"description": "Test Description"');
15
+ // Check order: Role -> Output Schema -> Task
16
+ const roleIndex = text.indexOf('# **Role**');
17
+ const schemaIndex = text.indexOf('# **Output Schema**');
18
+ const taskIndex = text.indexOf('# **Task**');
19
+ expect(roleIndex).toBeLessThan(schemaIndex);
20
+ expect(schemaIndex).toBeLessThan(taskIndex);
21
+ });
22
+ });
@@ -1,4 +1,3 @@
1
- import { type Observable } from 'rxjs';
2
1
  import type { AfterResolve } from '../../injector/index.js';
3
2
  import { afterResolve } from '../../injector/index.js';
4
3
  import type { Record } from '../../types/index.js';
@@ -36,7 +35,7 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
36
35
  * Observable for authentication errors.
37
36
  * Emits when a refresh fails.
38
37
  */
39
- readonly error$: Observable<Error>;
38
+ readonly error$: import("rxjs").Observable<Error>;
40
39
  /** Current token */
41
40
  readonly token: import("../../signals/api.js").WritableSignal<TokenPayload<AdditionalTokenPayload> | undefined>;
42
41
  /** Current raw token */
@@ -58,23 +57,23 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
58
57
  /** Whether the user is impersonated */
59
58
  readonly impersonated: import("../../signals/api.js").Signal<boolean>;
60
59
  /** Current token */
61
- readonly token$: Observable<TokenPayload<AdditionalTokenPayload> | undefined>;
60
+ readonly token$: import("rxjs").Observable<TokenPayload<AdditionalTokenPayload> | undefined>;
62
61
  /** Emits when token is available (not undefined) */
63
- readonly definedToken$: Observable<Exclude<TokenPayload<AdditionalTokenPayload>, void | undefined>>;
62
+ readonly definedToken$: import("rxjs").Observable<Exclude<TokenPayload<AdditionalTokenPayload>, void | undefined>>;
64
63
  /** Emits when a valid token is available (not undefined and not expired) */
65
- readonly validToken$: Observable<Exclude<TokenPayload<AdditionalTokenPayload>, void | undefined>>;
64
+ readonly validToken$: import("rxjs").Observable<Exclude<TokenPayload<AdditionalTokenPayload>, void | undefined>>;
66
65
  /** Current subject */
67
- readonly subjectId$: Observable<string | undefined>;
66
+ readonly subjectId$: import("rxjs").Observable<string | undefined>;
68
67
  /** Emits when subject is available */
69
- readonly definedSubjectId$: Observable<string>;
68
+ readonly definedSubjectId$: import("rxjs").Observable<string>;
70
69
  /** Current session id */
71
- readonly sessionId$: Observable<string | undefined>;
70
+ readonly sessionId$: import("rxjs").Observable<string | undefined>;
72
71
  /** Emits when session id is available */
73
- readonly definedSessionId$: Observable<string>;
72
+ readonly definedSessionId$: import("rxjs").Observable<string>;
74
73
  /** Whether the user is logged in */
75
- readonly isLoggedIn$: Observable<boolean>;
74
+ readonly isLoggedIn$: import("rxjs").Observable<boolean>;
76
75
  /** Emits when the user logs out */
77
- readonly loggedOut$: Observable<void>;
76
+ readonly loggedOut$: import("rxjs").Observable<void>;
78
77
  private get authenticationData();
79
78
  private set authenticationData(value);
80
79
  private get impersonatorAuthenticationData();
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { Subject, filter, firstValueFrom, from, map, race, skip, takeUntil, timer } from 'rxjs';
10
+ import { Subject, filter, firstValueFrom, from, race, skip, takeUntil, timer } from 'rxjs';
11
11
  import { CancellationSignal } from '../../cancellation/token.js';
12
12
  import { BadRequestError } from '../../errors/bad-request.error.js';
13
13
  import { ForbiddenError } from '../../errors/forbidden.error.js';
@@ -398,76 +398,60 @@ let AuthenticationClientService = class AuthenticationClientService {
398
398
  if (this.isLoggedIn()) {
399
399
  await this.syncClock();
400
400
  }
401
+ // Helper to sleep until the delay passes OR a vital state change occurs
402
+ const waitForNextAction = async (delayMs, referenceExp) => await firstValueFrom(race([
403
+ timer(Math.max(10, delayMs)),
404
+ from(this.disposeSignal),
405
+ this.token$.pipe(filter((t) => t?.exp !== referenceExp)),
406
+ this.forceRefreshRequested$.pipe(filter(Boolean),
407
+ // Skip the current value if already true to prevent infinite tight loops
408
+ skip(this.forceRefreshRequested() ? 1 : 0)),
409
+ ]), { defaultValue: undefined });
401
410
  while (this.disposeSignal.isUnset) {
402
- const iterationToken = this.token();
411
+ const token = this.token();
412
+ // 1. Wait for login/token if none is present
413
+ if (isUndefined(token)) {
414
+ await firstValueFrom(race([this.definedToken$, from(this.disposeSignal)]), { defaultValue: undefined });
415
+ continue;
416
+ }
403
417
  try {
404
- const token = iterationToken;
405
- if (isUndefined(token)) {
406
- // Wait for login or dispose.
407
- // We ignore forceRefreshToken here because we can't refresh without a token.
408
- await firstValueFrom(race([this.definedToken$, from(this.disposeSignal)]), { defaultValue: undefined });
409
- continue;
410
- }
411
418
  const now = this.estimatedServerTimestampSeconds();
412
- const forceRefresh = this.forceRefreshRequested();
413
- const refreshBufferSeconds = calculateRefreshBufferSeconds(token);
414
- const needsRefresh = forceRefresh || (now >= (token.exp - refreshBufferSeconds));
419
+ const buffer = calculateRefreshBufferSeconds(token);
420
+ const needsRefresh = this.forceRefreshRequested() || (now >= token.exp - buffer);
421
+ // 2. Handle token refresh
415
422
  if (needsRefresh) {
416
423
  const lockResult = await this.lock.tryUse(undefined, async () => {
417
424
  const currentToken = this.token();
425
+ if (isUndefined(currentToken)) {
426
+ return;
427
+ }
428
+ // Passive Sync: Verify if refresh is still needed once lock is acquired
418
429
  const currentNow = this.estimatedServerTimestampSeconds();
419
- const currentRefreshBufferSeconds = isDefined(currentToken) ? calculateRefreshBufferSeconds(currentToken) : 0;
420
- // Passive Sync: Check if another tab refreshed the token while we were waiting for the lock (or trying to get it)
421
- const stillNeedsRefresh = isDefined(currentToken) && (this.forceRefreshRequested() || (currentNow >= (currentToken.exp - currentRefreshBufferSeconds)));
430
+ const currentBuffer = calculateRefreshBufferSeconds(currentToken);
431
+ const stillNeedsRefresh = this.forceRefreshRequested() || (currentNow >= currentToken.exp - currentBuffer);
422
432
  if (stillNeedsRefresh) {
423
433
  await this.refresh();
424
434
  }
425
- if (this.forceRefreshRequested() && (this.token()?.jti != currentToken?.jti)) {
426
- this.forceRefreshRequested.set(false);
427
- }
435
+ this.forceRefreshRequested.set(false);
428
436
  });
437
+ // If lock is held by another instance/tab, wait briefly for it to finish (passive sync)
429
438
  if (!lockResult.success) {
430
- // Lock held by another instance, wait 5 seconds or until token changes (Passive Sync)
431
- const changeReason = await firstValueFrom(race([
432
- timer(5000).pipe(map(() => 'timer')),
433
- this.token$.pipe(filter((t) => t?.jti != token.jti), map(() => 'token')),
434
- from(this.disposeSignal),
435
- ]), { defaultValue: undefined });
436
- if (changeReason == 'token') {
439
+ await waitForNextAction(5000, token.exp);
440
+ // If another tab successfully refreshed, the expiration will have changed
441
+ if (this.token()?.exp !== token.exp) {
437
442
  this.forceRefreshRequested.set(false);
438
443
  }
439
- continue;
440
444
  }
445
+ continue; // Re-evaluate the loop with the newly refreshed (or synced) token
441
446
  }
442
- const currentToken = this.token();
443
- if (isUndefined(currentToken)) {
444
- continue;
445
- }
446
- const currentRefreshBufferSeconds = calculateRefreshBufferSeconds(currentToken);
447
- const delay = clamp((currentToken.exp - this.estimatedServerTimestampSeconds() - currentRefreshBufferSeconds) * millisecondsPerSecond, minRefreshDelay, maxRefreshDelay);
448
- const wakeUpSignals = [
449
- from(this.disposeSignal),
450
- this.token$.pipe(filter((t) => t?.jti != currentToken.jti)),
451
- ];
452
- if (!forceRefresh) {
453
- wakeUpSignals.push(this.forceRefreshRequested$.pipe(filter((requested) => requested)));
454
- }
455
- if (delay > 0) {
456
- await firstValueFrom(race([timer(delay), ...wakeUpSignals]), { defaultValue: undefined });
457
- }
458
- else {
459
- await firstValueFrom(race([timer(2500), ...wakeUpSignals]), { defaultValue: undefined });
460
- }
447
+ // 3. Calculate delay and sleep until the next scheduled refresh window
448
+ const timeUntilRefreshMs = (token.exp - this.estimatedServerTimestampSeconds() - buffer) * millisecondsPerSecond;
449
+ const delay = clamp(timeUntilRefreshMs, minRefreshDelay, maxRefreshDelay);
450
+ await waitForNextAction(delay, token.exp);
461
451
  }
462
452
  catch (error) {
463
453
  this.logger.error(error);
464
- const currentToken = this.token();
465
- await firstValueFrom(race([
466
- timer(2500),
467
- from(this.disposeSignal),
468
- this.token$.pipe(filter((t) => t?.jti != currentToken?.jti)),
469
- this.forceRefreshRequested$.pipe(filter((requested) => requested), skip(this.forceRefreshRequested() ? 1 : 0)),
470
- ]), { defaultValue: undefined });
454
+ await waitForNextAction(2500, token.exp);
471
455
  }
472
456
  }
473
457
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.149",
3
+ "version": "0.93.151",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,7 +1,7 @@
1
1
  import { NotSupportedError } from '../../errors/not-supported.error.js';
2
2
  import { filterUndefinedObjectProperties, fromEntries, hasOwnProperty, objectEntries } from '../../utils/object/object.js';
3
3
  import { isDefined, isNotNull, isNumber, isString } from '../../utils/type-guards.js';
4
- import { ArraySchema, BooleanSchema, DateSchema, DefaultSchema, EnumerationSchema, LiteralSchema, nullable, NullableSchema, NumberSchema, ObjectSchema, OptionalSchema, StringSchema, TransformSchema, Uint8ArraySchema, UnionSchema } from '../schemas/index.js';
4
+ import { AnySchema, ArraySchema, BooleanSchema, DateSchema, DefaultSchema, EnumerationSchema, LiteralSchema, NullableSchema, NumberSchema, ObjectSchema, OptionalSchema, StringSchema, TransformSchema, Uint8ArraySchema, UnionSchema, UnknownSchema } from '../schemas/index.js';
5
5
  import { schemaTestableToSchema } from '../testable.js';
6
6
  export function convertToOpenApiSchema(testable) {
7
7
  const schema = schemaTestableToSchema(testable);
@@ -17,7 +17,7 @@ export function convertToOpenApiSchema(testable) {
17
17
  function convertToOpenApiSchemaBase(schema) {
18
18
  if (schema instanceof ObjectSchema) {
19
19
  const entries = objectEntries(schema.properties);
20
- const convertedEntries = entries.map(([property, propertySchema]) => [property, convertToOpenApiSchema(stripOptional(propertySchema))]);
20
+ const convertedEntries = entries.map(([property, propertySchema]) => [property, convertToOpenApiSchema(propertySchema)]);
21
21
  const required = entries
22
22
  .filter(([, propertySchema]) => !(propertySchema instanceof OptionalSchema) && !((propertySchema instanceof NullableSchema) && (propertySchema.schema instanceof OptionalSchema)))
23
23
  .map(([property]) => property);
@@ -29,13 +29,13 @@ function convertToOpenApiSchemaBase(schema) {
29
29
  maxProperties: isNumber(schema.maximumPropertiesCount) ? schema.maximumPropertiesCount : undefined,
30
30
  });
31
31
  }
32
- if (schema instanceof DefaultSchema) { // You'd need to import DefaultedSchema
32
+ if (schema instanceof DefaultSchema) {
33
33
  return {
34
34
  ...convertToOpenApiSchema(schema.schema),
35
35
  default: schema.defaultValue,
36
36
  };
37
37
  }
38
- if (schema instanceof TransformSchema) { // You'd need to import TransformSchema
38
+ if (schema instanceof TransformSchema) {
39
39
  return convertToOpenApiSchema(schema.schema);
40
40
  }
41
41
  if (schema instanceof StringSchema) {
@@ -114,19 +114,16 @@ function convertToOpenApiSchemaBase(schema) {
114
114
  nullable: true,
115
115
  };
116
116
  }
117
+ if (schema instanceof OptionalSchema) {
118
+ return convertToOpenApiSchema(schema.schema);
119
+ }
117
120
  if (schema instanceof UnionSchema) {
118
121
  return {
119
122
  anyOf: schema.schemas.map((innerSchema) => convertToOpenApiSchema(innerSchema)),
120
123
  };
121
124
  }
122
- throw new NotSupportedError(`Schema "${schema.name}" not supported.`);
123
- }
124
- function stripOptional(schema) {
125
- if ((schema instanceof OptionalSchema)) {
126
- return schema.schema;
127
- }
128
- if ((schema instanceof NullableSchema) && (schema.schema instanceof OptionalSchema)) {
129
- return nullable(schema.schema.schema);
125
+ if ((schema instanceof AnySchema) || (schema instanceof UnknownSchema)) {
126
+ return {};
130
127
  }
131
- return schema;
128
+ throw new NotSupportedError(`Schema "${schema.name}" not supported.`);
132
129
  }